I am trying to use microsoft365 and oauth to get an access and refresh token. According to Microsoft 365 docs, we need to use the "offline_access" scope to get a refresh token along with access token. However, The response I am getting does not contain a refresh token.
Here is the code I used:
url = "https://login.microsoftonline.com/{}/oauth2/v2.0/token".format(tenant_id)
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
data = {
"client_id": client_id,
"client_secret": client_secret,
"grant_type": "client_credentials",
"scope": "https://graph.microsoft.com/.default offline_access",
}
response = requests.post(url, headers=headers, data=data)
The response contains the access token like usual, but does not contain the refresh token despite using the offline_access scope. Could someone kindly tell me what the issue is?
I agree with #junnas Client Credential Flow doesn’t return refresh token as user interaction is not present.
I tried to reproduce the same in my environment and got the results like below:
To get the refresh token, you need to choose user interactive flows such as Auth-Code Flow.
I created an Azure AD Application and added API permissions like below:
I generated auth-code using below endpoint:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/authorize?
client_id=ClientID
&response_type=code
&redirect_uri=https://jwt.ms
&response_mode=query
&scope=https://graph.microsoft.com/.default offline_access
&state=12345
I generated the access token and refresh token using below parameters:
GET https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default offline_access
grant_type:authorization_code
redirect_uri:redirectURi
code:code
By using the above generated refresh token, I refreshed the access token successfully like below:
GET https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientID
grant_type:refresh_token
refresh_token:xxx
client_secret:xxx
You are using client credentials flow. It does not return a refresh token because you don't need one.
Refresh tokens are involved when a user logs in to your application.
Since doing the authentication again would require that the user does the log in again, instead you get a refresh token to get new tokens.
But in the client credentials case re-authenticating is just a matter of sending the same request again.
Refresh token would add nothing to this.
Send the same request again before your token expires.
Related
I'm having trouble to allow users to logout from an application that uses Keycloak for access management.
I have found this topic being discussed here and there, but not clear instructions on how to handle the logout.
I tried to cause the logout of an user redirecting the browser to an endpoint of the following format:
https://example.com/auth/realms/myrealm/protocol/openid-connect/logout?id_token_hint=mytoken&post_logout_redirect_uri=https://example.com/initialpage/
What I used as "mytoken" was the access_token I had obtained making a post request to the endpoint:
https://example.com/auth/realms/playipintern/protocol/openid-connect/token
passing to it parameters like the ones bellow:
grant_type="authorization_code"
code=code_obtained_from_a_url_to_which_keycloak_redirected_the_browser
client_id=client_id_created_using_key_cloak_gui
redirect_uri=the_to_which_keycloak_redirected_the_browser
and reading the body of the response. The content of the body was a json, like the one bellow:
{
'access_token': 'long_token_I_used_latter_as_token_hint_trying_to_logout',
'expires_in': 300,
'refresh_expires_in': 1800,
'refresh_token': 'other_long_token',
'token_type': 'bearer',
'not-before-policy': 0,
'session_state': 'a_shorter_code',
'scope': 'email profile'
}
My logout attempt resulted in the following message in Keycloaks log:
22:53:51,686 WARN [org.keycloak.events] (default task-24) type=LOGOUT_ERROR, realmId=playipintern, clientId=null, userId=null, ipAddress=192.168.16.1, error=invalid_token
and the response said "We are sorry, session not active".
Now I'm aware that I should have used the id_token and not the access_token to logout, but received no id_token in the json.
Somewhere, someone said I should have included
scope=openid
in the parameters that I used to obtain the token. I did it, expecting to find an "id_token" field in the json, but nothing changed.
Someone else reported to have needed to create a scope (I believe using Keycloak's GUI) named "openid" to obtain the token. That didn't make much sense to me, but I tried it anyway and added the just created scope to the client scopes using Keycloak's GUI again. Oncemore, the json didn't change.
I tried to use the refresh_token as the id_token, but that also resulted in an invalid token message.
I don't know what to try now. Any help is appreciated.
Thank you.
/token endpoint returns only the access token by default. No refresh token is returned and no user session is created on the Keycloak side upon successful authentication by default. Due to the lack of refresh token, re-authentication is required when the access token expires. However, this situation does not mean any additional overhead for the Keycloak server because sessions are not created by default.
In this situation, logout is unnecessary. However, issued access tokens can be revoked by sending requests to the OAuth2 Revocation Endpoint as described in the OpenID Connect Endpoints section:
/realms/{realm-name}/protocol/openid-connect/revoke
Example:
POST /revoke HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
token=45ghiukldjahdnhzdauz&token_type_hint=access_token
You need to put your token in place of 45ghiukldjahdnhzdauz.
token_type_hint can take either access_token or refresh_token as value to define which type of token you want to revoke.
You will have to add scope=openid to your initial request to http://example.com/auth/realms/playipintern/protocol/openid-connect/auth (note the /auth instead of /token at the end) before the redirect from where you copied the access code.
You can find further information and explanation in this article.
I followed this tutorial EXACTLY, however
#app.route('/test')
def test_api_request():
if 'credentials' not in flask.session:
return flask.redirect('authorize')
# Load credentials from the session.
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
drive = googleapiclient.discovery.build(
API_SERVICE_NAME, API_VERSION, credentials=credentials)
files = drive.files().list().execute()
# Save credentials back to session in case access token was refreshed.
# ACTION ITEM: In a production app, you likely want to save these
# credentials in a persistent database instead.
flask.session['credentials'] = credentials_to_dict(credentials)
return flask.jsonify(**files)
However in this part:
credentials = google.oauth2.credentials.Credentials(
**flask.session['credentials'])
The refresh token expires after an hour, with this error:
The credentials do not contain the necessary fields need to refresh the access token. You must specify refresh_token, token_uri, client_id, and client_secret.
But clearly in the flask session the dict object is there:
{'client_id': '<COMMENTED_OUT>.apps.googleusercontent.com',
'client_secret': '<COMMENTED_OUT>',
'refresh_token': None,
'scopes': ['https://spreadsheets.google.com/feeds',
'https://www.googleapis.com/auth/drive',
'https://mail.google.com/'],
'token': '<COMMENTED_OUT>',
'token_uri': 'https://oauth2.googleapis.com/token'}
I believe the google tutorial auto-refreshes the token
Two questions
1) Do i need to manually "refresh" the refresh token? The comment in the tutorial says "Save credentials back to session in case access token was refreshed".. which implies that it's refreshed automatically
2) Is this because the app is still in unverified status?
Looking at the dict, the refresh token is missing:
'refresh_token': None,
You need this token in order to refresh your access token after it expires. The refresh token is only provided in the JSON response if the user saw a consent screen (the one that lists the scopes being requested). If the user has previously approved access, and the scopes haven't changed, the OAuth flow will skip that screen if the user is sent back into the flow, and therefore not return the refresh token.
What likely happened is that during your testing you approved access once, but didn't store the refresh token correctly. Further attempts to approve access didn't return the refresh token, hence your inability to refresh the access token.
To ensure a refresh token is always returned, set the URL parameter prompt=consent in the authorization URL:
authorization_url, state = flow.authorization_url(
access_type='offline',
include_granted_scopes='true'
prompt='consent')
(It's documented in the "HTTP/REST" tab here).
Alternatively, visit and revoke access to your application. The next time you go through the OAuth flow you should see the consent screen again, and get a new refresh token.
I have a client API, that is a confidential client. When I authenticate with an open id provider, I am redirected to my callback with an authorization code, which is immediately exchanged to receive a refresh token, an access token, and an ID token.
Now, I create a session cookie that has a uuid for the authenticated user. When the user makes a request, do I...
Use my access token to call the providers userinfo endpoint to get the user info.
Read the validated ID token to get the users info.
When it comes to using the refresh token I see 2 options:
After reading a valid ID token or access token during a request, use the refresh token to get a new access or ID token to store at a new uuid, which is returned to the user with an updated cookie. While requiring the user to sign in more, this means the users session becomes invalid after inactivity on their part equaling the lifetime of the access or ID token. This is potentially more secure.
Use the ID token or access token until valid and then refresh to get a new one. If the refresh never expires, the user will never have to sign in again even if inactive for a long period of time ( unless cookie expiration is low ) Potentially less secure.
Thoughts?
A few notes first:
the lifetime of the application session is (typically) independent of the lifetime of the ID token; the latter is just an assertion about the user's identity, it doesn't represent a session
your first option doesn't work with a parallel requests e.g. when a user has opened multiple tabs to your application or the application uses Javascript calls
But foremost: a refresh token should not be used to get a new ID token, it should only refresh the access token; a user needs to be present to get a new ID token with the same semantics as the original one.
In short, you only use an authentication token to access userinfo_endpoint uri.
OpenID Connect allows the use of a "Discovery document," a JSON document found at a well-known location containing key-value pairs which provide details about the OpenID Connect provider's configuration, including the URIs of the authorization, token, revocation, userinfo, and public-keys endpoints.
You can research each applications unique discovery page uri from their docs for example here is
Google
You make a get request to the discovery document uri and from this document you find the userinfo_endpoint uri.
Example response from microsoft
GET https://login.microsoftonline.com/organizations/v2.0/.well-known/openid-configuration
{
"authorization_endpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize",
"token_endpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"private_key_jwt"
],
"jwks_uri": "https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys",
"userinfo_endpoint": "https://graph.microsoft.com/oidc/userinfo",
"subject_types_supported": [
"pairwise"
],
...
}
Google's discovery doc uri
GET https://accounts.google.com/.well-known/openid-configuration
Get an Authorization token. For example pull up Network -> Fetch/ XHR now look around and try to find a request header with the key 'authorization'. Copy 'Bearer {the id}' and put in the header of a get request like the picture shown below.
GET or POST /oidc/userinfo HTTP/1.1
Host: graph.microsoft.com
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6Il…
Microsoft Example Postman Request
I am trying to write the exchange and access endpoints and the docs here (https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/linking-an-alexa-user-with-a-user-in-your-system#h2_login) are not clear on a couple of things:
how is the call to exchange a grant code for access token made - is it GET with credentials in QS or is it a POST with credentials in a body?
Is the access token delivered only in the JSON for an intent call or is it set properly as a bearer token?
It's a POST with credentials in the request body. Amazon follows the Oauth2 RFC correctly in this case.
The access token is delivered by Amazon only in the JSON for the intent request and not properly set as a bearer. This is annoying.
In my case, I had to hack around it by first validating if the request was a valid alexa request which contained a session with an access token, then setting the HTTP_AUTHORIZATION header to Bearer <token>, then using existing request auth logic to authenticate (I was using Django with django-oauth-toolkit, so YMMV if you're using something else).
That code looks something like this:
# get the access_token from the POST request
if access_token is not None:
request.META["HTTP_AUTHORIZATION"] = "Bearer " + access_token
if not hasattr(request, 'user') or request.user.is_anonymous():
user = authenticate(request=request)
if user:
request.user = request._cached_user = user
if request.user.is_authenticated():
# Do whatever with the logged in user
This is a follow up to question Not receiving Google OAuth refresh token
The answer is "The refresh_token is only provided on the first authorization from the user."
After revoking rights for the app and trying to authorize again, refresh token is not returned. What I get is:
{
"access_token" : "XXXX..",
"token_type" : "Bearer",
"expires_in" : 3600,
"id_token" : "XXXX..."
}
Others suggested to use access_type=offline however, according to description offline access is used if:
"application needs to access a Google API when the user is not present
at the browser"
which isn't the case for me.
What is a proper way to get refresh token?
You only get a refresh token if access_type=offline is set. You have two choices of how to handle this:
Don't use access_type=offline. Your access token will be good for 1 hour. After the access token expires, re-prompt the user to authenticate again. They'll need to do the whole OAuth dance again so that you can get a new access token.
Use access_type=offline so that you can get a new access tokens via the refresh token. If you prefer, after the user logs out, you can revoke the tokens.
Every time When you reload you application page , Access Token is Refreshed or you can say the refresh token for this purpose you should use the following but First You Need the authentication
gapi.auth.getToken().access_token;
i am also doing the same thing by the following Way
var accessToken = gapi.auth.getToken().access_token;
var xhr = new XMLHttpRequest();
xhr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
You can use this to avoid your problem "refresh token is not returned".
Thank You!!