How to get refresh token after authenticate via pkce flutter app with keycloak using openid_client? - flutter

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

Related

How to logout with openid_client after authentication via pkce in flutter app with keycloak using openid_client?

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.

Cannot authenticate via pkce flutter app with keycloak using openid_client

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)

Token is generated at the endpoint but does not arrive on the page

I want to create a website with Svelte/Kit and use JWT.
I have found instructions on the internet, for example:
Svelte JWT Authentication https://morioh.com/p/1d95522418b2
SvelteKit Session Authentication Using Cookies https://www.youtube.com/watch?v=bG7cxwBMVag
But unfortunately no instructions for Svelte Kit and JWT. So I tried it myself.
The token is generated at the endpoint, but does not arrive on the page (or is not callable). I suspect that some setting in the headers is wrong, but can't figure out what is wrong. This is my highly simplified test environment:
(1) I call the endpoint login.js from the page index.svelte. For testing, I omit checking email and password and send JWT right back. Data arrives, but I don't see the JWT.
(2) The JWT should be sent to another endpoint. What is the best way to do this?
The "page" index.svelte (simplified):
<script>
let email="", password="";
const doLogin = async () => {
const response = await fetch("/auth/login", {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
credentials: 'include',
body: JSON.stringify({
email,
password
})
});
if (response.status == 200) {
const { done, value } =
await response.body.getReader().read();
await console.log("done, value=", done,
JSON.parse(new TextDecoder("utf-8").decode(value)));
await console.log("headers=", response.headers);
}
}
</script>
<h1>Welcome to MyAuth</h1>
<input type=email bind:value={email}/><br/>
<input type=password bind:value={password}/><br/>
<button on:click={doLogin}>Submit</button>
The "endpoint" login.js (simplified):
import jwt from "jsonwebtoken";
export function post(request, context) {
const token = jwt.sign({
data: { text: "test" },
"topsecret",
});
const response = {
status: 200,
headers: {
'content-type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: {
passwordOk: true,
}
};
return response;
}
The console shows:
done, value= false {passwordOk: true}
index.svelte:59 headers= HeadersĀ {}
index.svelte:44 Fetch finished loading: POST "http://localhost:3000/auth/login".
doLogin # index.svelte:44
I think you are mixing up the two major parts to authentication:
Requesting/sending credentials.
Using those credentials to access protected content.
Authorization: Bearer ${token} is normally sent from the (browser) client to the server to request access to protected content. So right now, your server is asking the client for permission. This doesn't make sense.
Instead, the login endpoint should send the token via:
Set-Cookie header in the login endpoint.
The body of the response (where passwordOk is).
Set-Cookie causes the browser to send this value as a cookie with every future request. The server can check for this cookie value before serving protected content. This can be more secure because you can send an HTTP only cookie.
If the token is sent in the body of the response to login the client should send the token in future requests with the Authorization: Bearer ${token} header. The server can then check for this header before serving protected content.

OAuth invalid_grant error on coinbase using oauth2_client flutter package

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

Apple Sign In with Flutter and Auth0

I have a flutter app and use this package to implement Apple Sign In feature: https://pub.dev/packages/sign_in_with_apple
I get the authorization data from the Apple like below:
userIdentifier = "0XXXX7.6bb65XXXXXXXXXXXXXXXX36.1XXX2"
givenName = "test"
familyName = "signing"
email = "testemail#company.com"
authorizationCode = "c372xxxxxxxa526eexxxxxx1111e.0.rwsex.SwXxxXXXdDj_XxxXXXxxX"
identityToken = "eyJraXxxuxjxxxXxxxXx.eyJXxxxxtXxxxxxxhlYXxxXXxXxXXXXX"
state = null
Then I tries to send the authorizationCode as described here in Step 3: https://auth0.com/docs/connections/nativesocial/apple
And I get the 403 Forbidded {"error":"invalid_grant","error_description":"Invalid authorization code"}
I have configured the settings in the Auth0 dashobard in Social and Applications section.
The login process works well in the web environment but I can not do it in the Flutter.
Could anyone help me with what should I do with the authorizationCode to perform successful login and get Access Token and ID Token from Auth0 in the native app?
Rather a late answer, but I've found following the documentation for the /oauth/token end-point titled "Token Exchange for Native Social" works for me.
Specifically sending that end-point a POST, eg. with this in Dart code:
final appleAuthorizationCode = '???????????????????????';
const url = 'https://$AUTH0_DOMAIN/oauth/token';
final uri = Uri.parse(url);
final result = await http.post(
uri,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: {
"client_id": AUTH0_CLIENT_ID,
"grant_type": 'urn:ietf:params:oauth:grant-type:token-exchange',
"subject_token": appleAuthorizationCode,
"subject_token_type": 'http://auth0.com/oauth/token-type/apple-authz-code',
"scope": 'openid profile offline_access',
},
encoding: Encoding.getByName('utf-8'),
);
print('AUTH0 result: $result');
works for me.