How to decode access token from Microsoft OAuth2 - cakephp

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

Related

How to manage JWT properly using React?

I am builing a member system with a lot of function like memo or post system...etc, so I think it's more safe to use JWT token, so I let my api return jwt token every time I sign in like below
{
"status": 200,
"message": "",
"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MGViYzI0MDRhNmVkNDU2NzUwOTk4YjEiLCJ1c2VybmFtZSI6ImpvaG4iLCJleHAiOjE2MjY5NTgwMzEuMjA3LCJpYXQiOjE2MjYzNTMyMzF9.3t_YzKPq4jk6UuIkzTgFaLoXD0Pq5ktmRFp7xg6dFYU"
}
And it contains userID and userName , but here's the problem , every time I have to use something else like userProfilePicture ,userFriend...etc , I have to make an API request, it's really meaningless to do
eventually I manage it like I used axio.interceptor to verify and before I assign my data to context state I fetch user's all data using this token and assign the data to state , Is'nt it the same way to just return all user's data without JWT?.
It seems to me that JWT Token is kind of useless, can anyone tell me that what's the real ,effiecent way to use JWT Token and what's the common managment?
Use can save your token JWT in localstorage... and call it in header of request (or fetch) when you call api...
But dont forget to set expired token of JWT to one week (or one month, or one year... its up to you)...
Or you can set configuration if your JWT, if your token is expired, it will still send the response to your app....

How can I log the generated Access Token in Identity Server 4?

I would like to know how we can log the generated Refresh & AccessToken in IdentityServer 4.
Currently, we've got the custom implementation about the JwtAccessToken and we writes it + userId/name to the central logging system whenever it generates a new Access token. For Apis (we've more than 10), it always writes all incoming requests + JwtToken to the same logging system. So, we can easily trace what the user had done and see the logs/values at that particular time.
Now, we are going to replace that custom security implementation with IDSV4 and we couldn't find out a way to log the generated token in IDSV4.
We know that we can get the Access Token in .Net App by using await HttpContext.GetAccessTokenAsync(). But we don't want to manually send a log from all our apps (.Net, Spas, Apis (Client Credentials)) which are going to integrate with IDSV. We want to manage that AccessToken logging in a central place as we did before.
I looked at the IDSV4 sourcecode TokenEndpoint.cs Line120, LogTokens()
if (response.IdentityToken != null)
{
_logger.LogTrace("Identity token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.IdentityToken);
}
if (response.RefreshToken != null)
{
_logger.LogTrace("Refresh token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.RefreshToken);
}
if (response.AccessToken != null)
{
_logger.LogTrace("Access token issued for {clientId} / {subjectId}: {token}", clientId, subjectId, response.AccessToken);
}
Actually, they write the TraceLogs for the actual tokens. But we don't want to update the log level to Trace because it'll flood our logging system.
So, I would like to know whether it's possible to implement a feature to write a generated tokens to a log whenever IDSV4 issues an AccessToken. Is there anyway to intercept these tokens after the generation?
Or do we have to manually log AccessTokens whenever it's generated or refreshed in all our clients?
Update:
Thanks to sellotape for giving me an idea for DI. The following is the correct class to intercept the generated Token:
public class CustomTokenResponseGenerator : TokenResponseGenerator
{
public CustomTokenResponseGenerator(ISystemClock clock, ITokenService tokenService, IRefreshTokenService refreshTokenService, IResourceStore resources, IClientStore clients, ILogger<TokenResponseGenerator> logger) : base(clock, tokenService, refreshTokenService, resources, clients, logger)
{
}
public override async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult request)
{
var result = await base.ProcessAsync(request);
// write custom loggings here
return result;
}
}
After that you can replace default class from IDSV4 with your custom class
services.Replace(ServiceDescriptor.Transient<ITokenResponseGenerator, CustomTokenResponseGenerator>());
There are many places to hook in for this; one is to create your own implementation of ITokenService by deriving from DefaultTokenService.
Override CreateAccessTokenAsync() and have it do:
Token result = await base.CreateAccessTokenAsync(request);
// Your logging goes here...
return result;
Swap in your version in your DI container at Startup (make sure it's after the default one has already been added):
services.Replace<ITokenService, MyTokenService>();
... and you should be ready.
As an aside, you should really log hashes of your tokens and not the tokens themselves. You can still match requests and actions to users based on the hash, but then at least nobody will be able to use the logging data to impersonate any of your users.

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 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)

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