ADAL v3 - How to properly get rid of refresh token code? - active-directory

In ADAL v2, we were doing this:
// Common parameter:
_clientCredential = new ClientAssertionCertificate(clientId, certificate);
// Get the token for the first time:
var userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
_authResult = await authContext.AcquireTokenAsync(resource, _clientCredential, userAssertion);
// Refresh the token (when needed):
_authResult = await authContext.AcquireTokenByRefreshTokenAsync(authResult.RefreshToken, _clientCredential);
Note that in order to refresh the token, we only need the previous authentication result and the common client credential (_authResult and _clientCredential). This is very convenient.
ADAL v3 lacks AcquireTokenByRefreshTokenAsync, and here is the explanation. But that doesn't say, in concrete terms, what kind of change is needed.
Do we have to replay the first AcquireTokenAsync (and therefore keep resource, accessToken and userName stored somewhere in the program state)?
Or is there some way of getting an up-to-date token with only the common elements (_authResult and _clientCredential)?

The mechanism to use a refresh token is now provided by AcquireTokenSilentAsync. See AcquireTokenSilentAsync using a cached token using a cached token for patterns to use this.
Are you utilizing the [ADAL token Cache] (http://www.cloudidentity.com/blog/2013/10/01/getting-acquainted-with-adals-token-cache/)? It saves you from managing the underlying implementation details of using refresh tokens in your code and the issue you are facing.

The recommended approach for the on-behalf-of flow in ADAL 3.x is to use:
try
{
result = await ac.AcquireTokenSilentAsync(resource, clientId);
}
catch (AdalException adalException)
{
if (adalException.ErrorCode == AdalError.FailedToAcquireTokenSilently ||
adalException.ErrorCode == AdalError.InteractionRequired)
{
result = await ac. AcquireTokenAsync (resource, clientCredentials, userAssertion);
}
}
For more details see https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/Service-to-service-calls-on-behalf-of-the-user
Note that there are scenarios where you could have cached a refresh token acquired with ADAL.NET v2.x, and to help migrating from ADAL 2.x to MSAL.NET, we plan to re-introduce the AcquireTokenByRefreshToken in MSAL.NET (but not in ADAL 4.x)

Related

How to decode access token from Microsoft OAuth2

I am building a web application using CakePHP 4.1.
And it has authorization from Microsoft Active Directory(OAuth2).
What I am going to do is to decode the access token and redirect to Microsoft login page when it is expired.
So I can get token from authorization like this.
https://login.microsoftonline.com/TENANT_ID/oauth2/v2.0/authorize?client_id=CLIENT_ID&response_type=token&redirect_uri=LOGIN_REDIRECT_URI&response_mode=form_post&scope=offline_access https://graph.microsoft.com/.default'
I tried to decode using firebase/jwt, but I am not sure what should be $key.
JWT::decode($accessToken, $key, array('RS256'))
I can get the decoded result if I enter the token in jwt.ms
I don't have any special claims, so $key should be plain.
And I want to know if there is another way to decode jwt.
Firebase JWT only supports decoding with signature validation, so a key is always required.
You can manually decode the token, it's just JSON as base64, however without validation there's no way to know whether the information hasn't been forged, so I wouldn't trust any information obtained that way, not even the expiration time.
Usually OAuth APIs return an expiration time in the response for access token requests (and so does Microsofts Identity Platform API), which your app can store alongside the token and use for checking for possible token expiration.
Refreshing upon receiving invalid token errors from the API is usually an option too, then you don't need to care about the expiration time yourself at all.
I would certainly suggest any of those options over trusting arbitrary client data.
That being said, if you still want the unvalidated expiration time from the token, just do what Firebase JWT does internally:
$tks = \explode('.', $accessToken);
if (\count($tks) != 3) {
throw new \UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
throw new \UnexpectedValueException('Invalid header encoding');
}
if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
throw new \UnexpectedValueException('Invalid claims encoding');
}
if (false === ($sig = JWT::urlsafeB64Decode($cryptob64))) {
throw new \UnexpectedValueException('Invalid signature encoding');
}
https://github.com/firebase/php-jwt/blob/v5.2.0/src/JWT.php#L81-L94

CompactToken validation failed 80049228

Some users are getting this error back when trying to sign in using Microsoft Sign In in order to access mail via MS Graph. I've had both corporate users and personal (Hotmail.com) users both showing this error number but it works fine for most users.
This is the call:
https://login.microsoftonline.com/common/oauth2/v2.0/token
This is the error returned:
Code: InvalidAuthenticationToken
Message: CompactToken validation failed with reason code: 80049228
Any pointers? Where can I find a reference to this error number?
This means the token expired and it need to be refreshed. If you want to refresh it without user interaction you'll need a refresh_token which is returned when you obtain a token initially.
Here is how you can refresh it:
function refreshTokenIfNeeded(tokenObj){
let accessToken = oauth2.accessToken.create(tokenObj);
const EXPIRATION_WINDOW_IN_SECONDS = 300;
const { token } = accessToken;
const expirationTimeInSeconds = token.expires_at.getTime() / 1000;
const expirationWindowStart = expirationTimeInSeconds - EXPIRATION_WINDOW_IN_SECONDS;
const nowInSeconds = (new Date()).getTime() / 1000;
const shouldRefresh = nowInSeconds >= expirationWindowStart;
let promise = Promise.resolve(accessToken)
if (shouldRefresh) {
console.log("outlook365: token expired, refreshing...")
promise = accessToken.refresh()
}
return promise
}
Where tokenObj is the token object you store in your database.
Make sure it also has expires_at or otherwise oauth2.accessToken.create() will create it and calculate from the current moment in time.
More details can be found in this tutorial and in this github repo (this is where the code above was taken from)
Found a Solution To This
In my case, I was refreshing the token before using the access_token with Microsoft Graph API even once.
Once you successfully call https://login.microsoftonline.com/common/oauth2/v2.0/token You will get a refresh_token and an access_token, my guess is that you have been refreshing the token before using the first access token from the URL mentioned above.
Steps to Fix:
Call https://login.microsoftonline.com/common/oauth2/v2.0/token as you did before
Copy the access_token from the response and use it at least once with your Microsoft Graph API
Now you can copy the refresh_token (or once the access_token is expired) and exchange for a new access token
Enjoy your API integration
Smile :)
Reference:
Microsoft Authentication (Tokens) Docs - Including Refresh Token
OneDrive Refresh Token Answer

ADAL.js - Obtaining Microsoft Graph Access Token with id_token

I am attempting to integrate Azure AD login and Graph API into my angular2 website.
I have successfully implemented an ADAL login and redirect, built around a useful blog post here
From this I retrieved an id_token parameter that my adalservice can access. Currently this is acheived through a simple context.login() and catching the token in the redirect.
When I use this token to try and access Microsoft Graph, I receive an InvalidAuthenticationToken response stating Access Token validation failure.
I'm new to this stuff, so it could be that my call is intrinsically wrong, or that I lack certain permissions in AD, or my app reg lacks permissions. I've seen that I potentially need to request an access token with sufficient scope, yet I can find any examples of this.
Has anyone used this adalService library to obtain tokens for use with Graph API?
I found a solution to my problem.
I was using the wrong token. I had to acquire a token specifically for Graph API. This meant I would have to first log in and then call this.context.acquireToken() like below:
this.context.acquireToken("https://graph.microsoft.com", function (error, id_token) {
if (error || !id_token) {
console.log('ADAL error occurred: ' + error);
}
else {
this.graphAccessToken = id_token;
//Call graph API
}
}.bind(this)
);
It seems like it's essential that this process have 2 calls. Maybe someone can shed some light on whether I can immediately obtain a token with scope for the Graph API on login. Perhaps by setting required permissions for the app in Azure AD.
Just to have a clarity for all, updating the end to end solution here again.
In case you do not have the base starter code, refer to this link Adal-JS Tutorial. This post only concerns with the customization involved.
Step 1: Configure the AdalService
(only new code is shown, other methods remain as it is)
export class AdalService {
public get graphAccessToken() {
return sessionStorage[new AppConstants().User_Graph_Token];
}
public retrieveTokenForGraphAPI() {
this.context.acquireToken('https://graph.microsoft.com', function (error, graph_token) {
if (error || !graph_token) {
console.log('ADAL error occurred: ' + error);
} else {
// Store token in sessionStorage
sessionStorage[new AppConstants().User_Graph_Token] = graph_token;
return;
}
}.bind(this)
);
}
}
The code should have existing handlers for id_token callback and corresponding configuration in the routing. If not, please refer to link above for the initial code.
Now the requirement is retrieve the access_token once the id_token is retrieved. The access_token has additional field for "puid" which describes identifier for claims. This will be the step 2.
Step 2: Update LoginComponent
ngOnInit() {
if (!this.adalService.isAuthenticated) {
console.log('LoginComponent::Attempting login via adalService');
this.adalService.login();
} else {
if (this.adalService.accessTokenForGraph == null) {
console.log('LoginComponent::Login valid, attempting graph token retrieval');
this.adalService.retrieveTokenForGraphAPI();
}
}
Now the token is retrieved and stored for later use.
Step 3: Update Routing for 'access_token' callback
Similar to the 'id_token' callback, we need to add additional callback route for the access_token. The callback components will remain same. Their code is as described in the main link. Note that *access_token" endpoint is MS provided, hence be careful not to change the name.
{ path: 'access_token', component: OAuthCallbackComponent, canActivate: [OAuthCallbackHandler] },
{ path: 'id_token', component: OAuthCallbackComponent, canActivate: [OAuthCallbackHandler] }
Step 4: Use the token wherever required
const bearer = this.adalService.graphAccessToken();

how can I get refresh token

i learn this code sample :https://github.com/Azure-Samples/active-directory-dotnet-graphapi-web ,and yes ,i can get access token in AuthorizationCodeReceived :
AuthenticationHelper.token = result.AccessToken;
but how do i get the refresh token ?result.RefreshToken is not available , then how do i use acquiretokenbyrefreshtoken function ?
https://msdn.microsoft.com/en-us/library/microsoft.identitymodel.clients.activedirectory.authenticationcontext.acquiretokenbyrefreshtoken.aspx
The acquiretokenbyrefreshtoken function is available in ADAL 2.X , that code sample is using ADAL 3.13.8 , and from ADAL3.X, library won't expose refresh token and AuthenticationContext.AcquireTokenByRefreshToken function.
ADAL caches refresh token and will automatically use it whenever you call AcquireToken and the requested token need renewing(even you want to get new access token for different resource).
please see the explanation from here . Also click here and here for more details about refresh token in ADAL .
If you looking for a persistent mechanism, you can simply use TokenCache.Serialize()
Here's how I did it:
First, get the token and serialize the cache token
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{Tenant}");
var authResult = authContext.AcquireTokenAsync(resource, ClientId, new Uri("https://login.microsoftonline.com/common/oauth2/nativeclient"), new PlatformParameters(PromptBehavior.SelectAccount)).Result;
byte[] blobAuth = authContext.TokenCache.Serialize();
Then, load the cached bytes
AuthenticationContext authContext = new AuthenticationContext($"https://login.microsoftonline.com/{tenant}/");
authContext.TokenCache.Deserialize(blobAuth);
var res = authContext.AcquireTokenSilentAsync(resource, clientId).Result;

refreshToken is null

I used DREdit app's Oauth code to get accessToken and refreshToken for my app and i am getting the accessToken but refreshToken is coming null always.
I tried to print the values in the code which comes like below
authorization URL:https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=651991573332.apps.googleusercontent.com&redirect_uri=http://www.sakshum.org/GoogleOauth&response_type=code&scope=https://www.googleapis.com/auth/drive.file%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile
This code already has access_type=offline which I found not having in the url was the cause in some cases. Please advise what else could be wrong here.
The log prints as follows on appEngine
[s~sakshumweb-hrd/3.368699522239285323].<stdout>: Code:4/XQ1sR1Pu5VHDqGbG9iJO10bXVCCE.Qn-L1XwrBVYaEnp6UAPFm0EmSoCXfwI
W 2013-07-10 20:20:16.294
com.google.api.client.googleapis.services.AbstractGoogleClient <init>: Application name is not set. Call Builder#setApplicationName.
I 2013-07-10 20:20:16.536
[s~sakshumweb-hrd/3.368699522239285323].<stdout>: id:113470899999229420779
I 2013-07-10 20:20:17.936
[s~sakshumweb-hrd/3.368699522239285323].<stdout>: access token:ya29.AHES6ZSP7MXaaUhMz4RO7Jm3Zkh_s1zUxJyzW_6IvfADaQ
I 2013-07-10 20:20:17.936
[s~sakshumweb-hrd/3.368699522239285323].<stdout>: refresh token:null
Refresh tokens are only issued on the initial authorization (whenever the consent screen is shown.) If you find you're in a state where you don't have a saved refresh token for a user, you can ask for reauthorization with the added query parameter prompt=consent. The user will be asked to re-authorize and a new refresh token will be generated.
After your link you get authorization code. For instance:
https://accounts.google.com/o/oauth2/auth?access_type=offline
&approval_prompt=auto
&client_id=[your id]
&redirect_uri=[url]
&response_type=code
&scope=[access scopes]
&state=/profile
then if in future you are going to have access to drive you need refresh token (you can every time request auth token - redirecting to google and etc... but it's not good way. after first usage, you should save Credentials in Database with User UID (for instance, it may be mail). So if you need to have access to the drive in the feature you do this:
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
As I guess you also want to get url of the file which is in the Google Drive. when you get files - thee the documentation, then you will find download methids in com.google.api.services.drive.model.File object. read documentations

Resources