CompactToken validation failed 80049228 - microsoft-identity-platform

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

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

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

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)

IBM Watson tokens expire in one hour. Code to refresh the tokens?

I'm setting up IBM Watson Speech-to-Text. This requires an access token, documented here:
"Tokens have a time to live (TTL) of one hour, after which you can no longer use them to establish a connection with the service. Existing connections already established with the token are unaffected by the timeout. An attempt to pass an expired or invalid token elicits an HTTP 401 Unauthorized status code from DataPower. Your application code needs to be prepared to refresh the token in response to this return code."
I don't see on the documentation page an example of application code to refresh the token.
Should my code generate a new token for every user who downloads the JavaScript app? Or should the server request a new token every hour, and give all users the same token for an hour?
The shell command to get a token is:
curl -X GET --user username:password \
--output token \
"https://stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api"
That looks like I can send an HTTP request from my JavaScript app and get back a token. Should I request the token as a file, and then have my app get the token from the file?
enter code hereWhat worked was to use Cloud Functions for Firebase (part of Google Cloud Functions), triggered on a user logging in, running Node to send an HTTP request to get the token and then write the result to an AngularJS value service.
This Cloud Function works:
// Node modules
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const request = require('request'); // node module to send HTTP requests
const fs = require('fs');
admin.initializeApp(functions.config().firebase);
exports.getWatsonToken = functions.database.ref('userLoginEvent').onUpdate(event => { // authentication trigger when user logs in
var username = 'groucho',
password = 'swordfish',
url = 'https://' + username + ':' + password + '#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api';
request({url: url}, function (error, response, body) {
var tokenService = "app.value('watsonToken','" + body + "');";
fs.writeFile('../public/javascript/services/watsonTokenValue.js', tokenService, (err) => {
if (err) throw err;
console.log('The file has been saved!');
}); // close fs.writeFile
}); // close request
}); // close getWatsonToken
In the controller:
firebase.auth().onAuthStateChanged(function(user) { // this runs on login
if (user) { // user is signed in
console.log("User signed in!");
$scope.authData = user;
firebase.database().ref('userLoginEvent').update({'user': user.uid}); // update Firebase database to trigger Cloud Function to get a new IBM Watson token
} // end if user is signed in
else { // User is signed out
console.log("User signed out.");
}
}); // end onAuthStateChanged
Walking through the Cloud Function, it injects four Node modules, including request for sending HTTP requests, and fs for writing the results to a file. Then the trigger is set for an update to a location userLoginEvent in the Firebase database (which I created from the console). Next, the HTTP request goes out. The response (the token) is called body. app.value('watsonToken','" + body + "');" is an Angular value service to wrap the token. Then fs writes all this to a location in my project.
In the AngularJS controller, onAuthStateChanged triggers when a user logins in. The user.uid is then updated to the location userLoginEvent in the Firebase database, and the Cloud Function triggers, the HTTP request goes out, and the response is written to an Angular service.

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