I have the following KeyCloak Client config, to use pkce authentication flow:
Realm: REALM
Client ID: pkce-client
Client Protocol: openid-connect
Access Type: public
Standard Flow Enabled: ON
Valid Redirect URIs: http://localhost:4200/
Advanced Settings:
Proof Key for Code Exchange Code Challenge Method: S256
I try to authenticate in a flutter App with iOS Simulator via openid_client
https://pub.dev/packages/openid_client like this
authenticate() async {
var uri = Uri.parse('http://$localhost:8180/auth/realms/REALM');
var clientId = 'pkce-client';
var scopes = List<String>.of(['profile', 'openid']);
var port = 4200;
var redirectUri = Uri.parse(http://localhost:4200/);
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
var authenticator = new Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: urlLauncher,
redirectUri: redirectUri,
);
var auth = await authenticator.authorize();
var token= await auth.getTokenResponse();
return token;
}
But it only gives me this web view:
The Terminal where KeyCloak is running gives me the following lines:
INFO [org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker] (default task-34) PKCE enforced Client without code challenge method.
WARN [org.keycloak.events] (default task-34) type=LOGIN_ERROR, realmId=REALM, clientId=pkce-client, userId=null, ipAddress=127.0.0.1, error=invalid_request, response_type=code, redirect_uri=http://localhost:4200/, response_mode=query
When using Postman it worked and it provided me the Login page:
But I have additional parameters there, which I do not know where to add them in Authenticator(..) when using openid_client, state is automatically added.
state: <state>
code_challenge: <code-challenge>
code_challenge_method: S256
Do I need to add these code_challenge parameters somewhere in the openid_client method?
Or do I need to change the redirect URL when using an App? I tried with the package_name like proposed here (https://githubmemory.com/repo/appsup-dart/openid_client/issues/32), but it did not work either.
See the source code:
: flow = redirectUri == null
? Flow.authorizationCodeWithPKCE(client)
: Flow.authorizationCode(client)
You have specified redirectUri, so authorizationCode flow was used. But you want authorizationCodeWithPKCE flow in this case. So just make sure redirectUri is null and correct PKCE flow (with correct url parameters, e.g. code_challenge) will be used.
You need to send the redirectUri:null and set the port to 3000 ( you can use your port ).
after that you need to add the redirect uri in keycloak like this http://localhost:3000/ .it will do the trick
(what happening is when you send the redirect uri as null value, open_id client use the pkce flow and use the default url)
Related
I'm new to web sockets (specifically to socketIO interfaces, both client and server), and I wonder how basic JWT auth is usually implemented. How the headers are passed through. Should I add them to every socket listener or emit event? Or just once during the connection. Maybe the connection is already considered private because of the connection id?
I am using Socket.io-client v.4 and flask-socketio.
I am interested in general practices and would be grateful for any information or your own experience.
There are many ways to pass authentication when you connect:
HTTP header
Cookie
query string
auth option
To pass a token in a header (not valid when connecting directly via WebSocket):
const socket = io({
extraHeaders: {
"Header-Name": "abcd"
}
});
Same site cookies are always passed to the server. To pass cookies cross-site:
const socket = io("https://my-backend.com", {
withCredentials: true
});
To pass it in the query string:
const socket = io({
query: {
token: 'abcd'
}
});
To pass it with the auth option (Socket.IO v3 and up only):
const socket = io({
auth: {
token: "abcd"
}
});
I have the following KeyCloak Client config, to use pkce authentication flow:
Realm: REALM
Client ID: pkce-client
Client Protocol: openid-connect
Access Type: public
Standard Flow Enabled: ON
Valid Redirect URIs: http://localhost:4200/
Advanced Settings:
Proof Key for Code Exchange Code Challenge Method: S256
After authenticating with flutter App with iOS Simulator via openid_client
https://pub.dev/packages/openid_client at some point I need to log out.
I can do this to get the logout URL:
String localhost = getLocalhost();
var uri = Uri.parse('http://$localhost:8180/auth/realms/REALM');
var clientId = 'pkce-client';
var issuer = await Issuer.discover(uri);
var client = Client(issuer, clientId);
String idT = token.idToken.toCompactSerialization();
Credential credential = client.createCredential(
tokenType: token.tokenType,
refreshToken: token.refreshToken,
idToken: idT,
);
var url;
try {
url = credential.generateLogoutUrl();
} catch (e) {
print("Error during login (refresh) " + e.toString());
}
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
String callUrl = url.toString();
urlLauncher.call(callUrl);
This is how the logout url looks like:
http://localhost:8180/auth/realms/vopi/protocol/openid-connect/logout?id_token_hint=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxUVJwMXAtbmk1WmcyZmlyRHFoRS1iS1hwe.......
I'm not logged out after calling the url. Can someone help with this?
Thanks in advance
Redirect browser (web view) to that logout url (logout URL is not an API call, so you can't use XMLHttpRequest). That terminates existing IdP session. Of course you need to destroy also any local tokens (access/id/refresh token), which your app already has.
the IDP should have a front end logout url that you can call to logout of the current session. it is a call directly from the browser to the IDP endpoint.
the IDP front end logout should terminate the session, clear any cookies but the backend tokens (access token , refresh token etc) need to be cleared by your application.
I have the following KeyCloak Client config, to use pkce authentication flow:
Realm: REALM
Client ID: pkce-client
Client Protocol: openid-connect
Access Type: public
Standard Flow Enabled: ON
Valid Redirect URIs: http://localhost:4200/
Advanced Settings:
Proof Key for Code Exchange Code Challenge Method: S256
When authenticating with flutter App with iOS Simulator via openid_client
https://pub.dev/packages/openid_client like this
authenticate() async {
var uri = Uri.parse('http://$localhost:8180/auth/realms/REALM');
var clientId = 'pkce-client';
var scopes = List<String>.of(['profile', 'openid']);
var port = 4200;
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
var authenticator = new Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: urlLauncher,
);
var auth = await authenticator.authorize();
var token= await auth.getTokenResponse();
return token;
}
I get the following response:
How do I get a new access token with the refresh token?
I tried:
POST http://localhost:8180/auth/realms/REALM/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
client_id: pkce-client
grant_type: refresh_token
refresh_token: "received refresh token"
but I get:
{"error":"invalid_client","error_description":"Invalid client credentials"}
How do I need to prepare the request to refresh the access token?
Thanks in advance
One cause of the problem could be that you need to include the client_secret as well in the request. This might be needed if the client is a "confidential" client.
Se the discussion here for further details. Refresh access_token via refresh_token in Keycloak
I am using the oauth2_client package for flutter, connecting to the Coinbase API via OAuth 2.0.
From what I can tell, Coinbase uses the code flow for authentication. This is the same as Github. This is important to note because I can successfully auth into Github using the oauth2_client package for flutter.
To connect to Github I used the existing client:
import 'package:oauth2_client/oauth2_client.dart';
import 'package:meta/meta.dart';
/// Implements an OAuth2 client against GitHub
///
/// In order to use this client you need to first create a new OAuth2 App in the GittHub Developer Settings (https://github.com/settings/developers)
///
class GitHubOAuth2Client extends OAuth2Client {
GitHubOAuth2Client(
{#required String redirectUri, #required String customUriScheme})
: super(
authorizeUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
accessTokenRequestHeaders = {'Accept': 'application/json'};
}
}
Then I created a method to call within the app:
void _oauthMethod() async {
//clientID
String cID = 'x';
//clientSecret
String cSecret = 'y';
OAuth2Client client = GitHubOAuth2Client(
redirectUri: 'my.app://oauth2redirect', customUriScheme: 'my.app');
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID, clientSecret: cSecret, scopes: ['repo']);
http.Response resp = await http.get('https://api.github.com/user/repos',
headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
}
Calling this function brings up the OAuth page for Github, I can sign in, and if I print resp it shows a list of my repos. As expected.
Using the same method for Coinbase, I first create the new class:
class MyOAuth2Client extends OAuth2Client {
MyOAuth2Client(
{#required String redirectUri, #required String customUriScheme})
: super(
authorizeUrl:
'https://www.coinbase.com/oauth/authorize', //Your service's authorization url
tokenUrl:
'https://api.coinbase.com/oauth/token', //Your service access token url
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
this.accessTokenRequestHeaders = {'Accept': 'application/json'};
}
}
Then I create the method to call:
void _coinbaseAuth() async {
String cID = 'x';
String cSecret = 'y';
MyOAuth2Client client = MyOAuth2Client(
redirectUri: 'my.app://oauth2redirect', customUriScheme: 'my.app');
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID, clientSecret: cSecret, scopes: ['wallet:user:read']);
print(tknResp);
//code fails
//http.Response resp =
// await http.get('https://api.coinbase.com/v2/user', headers: {
// 'Authorization': 'Bearer ' + tknResp.accessToken,
// 'Content-Type': 'application/json',
// 'Charset': 'utf-8'
// });
}
I can't run the http.Response part, because it is filled with nulls. The tknResp prints:
HTTP 401 - invalid_grant The provided authorization grant is invalid,
expired, revoked, does not match the redirection URI used in the
authorization request, or was issued to another client.
I have tried creating a new OAuth application in Coinbase, however this doesn't work.
Does anyone know why I'm getting this error? It's confusing for me as the code worked with Github using the exact same OAuth flow.
I tested the auth flow manually using postman, which enabled me to get the token.
After some testing, I was able to get the token with the dart package by adding the extra auth code params & disabling PKCE
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID,
clientSecret: cSecret,
scopes: ["wallet:user:read"],
authCodeParams: {
"grant_type": "authorization_code",
"redirect_uri": "my.app://oauth2redirect"
},
enablePKCE: false,
state: 'OYWjs_95M6jlkvy5');
hi in my case i have problems in the get token with duplicate oauth of coinbase with my app to text.
error "invalid_grant"
To solve, I went to the test account and navigate to the activities section and click x (x) to log out and intend again.
Also applies to other oauth 2
thanks
im trying to integrate spring security with a custom angular 2 login, that is a specific endpoint of my app is protected with spring security, trying to access it will redirect to /login that is handled in angular 2. as things stands now i have no clue as to how to perform the login and grant access to the backend API once logged.
i am configuring spring security as follows:
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors().and()
.authorizeRequests()
.antMatchers("/api/someEndpoint/**")
.hasRole(ADMIN_ROLE).and().formLogin()
.loginPage("/login").and().logout();
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
as I had the default login everything worked fine, but I have found myself unable to create a working angular 2 login integration.
I tried the following code in angular 2 to no avail:
login(loginDetails:Object) {
console.log(loginDetails)
const headers = new Headers({ 'Content-Type': 'application/json' });
const options = new RequestOptions({ headers: headers });
const body = JSON.stringify(loginDetails);
console.log(headers);
console.log(body);
return this.http.post(this.loginUrl, body, options)
}
as far as I know spring security defaults for username and password variable names are "username" and "password", which i am sure are being passed in the request body so when passing some invalid user data like {"username":"admin", "password" : "pass"}I should be redirected to /login?error or something, and when successfully authenticated I should be redirected to /welcome and stay authenticated
I have the user and pass defined in my db and my custom userDetailsService checks against it
any answers, comments or questions are welcome
Once you're working with an API you've to use the HTTP Basic authentication.
It's also required to use HTTPS to prevent the main-in-middle attack.
To implement HTTP Basic with Angular the login service would look like this:
login (loginDetails: any): Observable<LoginResponse> { // custom class, may be empty for now
let headers = new Headers({
'Authorization': 'Basic ' + btoa(loginDetails.login + ':' + loginDetails.pass),
'X-Requested-With': 'XMLHttpRequest' // to suppress 401 browser popup
});
let options = new RequestOptions({
headers: headers
});
return this.http.post(this.loginUrl, {}, options)
.catch(e => this.handleError(e)); // handle 401 error - bad credentials
}
... then you subscribe this in the caller component:
loginNow() {
this
.loginService
.login(this.loginDetails)
.subscribe(next => {
this.router.navigateByUrl("/"); // login succeed
}, error => {
this.error = "Bad credentials"; // or extract smth from <error> object
});
}
Then you can use the loginNow() method inside component templates like (click)="loginNow().
As soon as the server will accept an authorization, JSESSIONID will be stored in your browser automatically because of Spring Security features and you won't be forced to send the credentials each time you access private resources.
Your login server method may look like this:
#PreAuthorize("hasRole('USER')")
#PostMapping("/login")
public ResponseEntity login() {
return new ResponseEntity<>(HttpStatus.OK);
}
... it would reject with 401 UNAUTHORIZED when the authorization fails or accept with 200 SUCCESS when it's not.
How to setup a server in the proper way there's a number of Spring Security demo projects present: https://github.com/spring-guides/tut-spring-security-and-angular-js
Your spring security config needs to look like this
http!!
.cors().and()
.csrf().disable()
.authorizeRequests()
.requestMatchers(object: RequestMatcher {
override fun matches(request: HttpServletRequest?): Boolean {
return CorsUtils.isCorsRequest(request)
}
}).permitAll()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin().permitAll()
I had a similar issue, but I had to override the successlogout handler as mentioned here.