I am following the guide to retrieving the id, access, and refresh token for a nodejs project. I am utilizing authorization_code flow, where the user logs in via the default auth0 account login(non-3rd party login).
When I make the request successfully I only receive the users access token, but not the id token.
I am making the request to the /oauth/token with the authorization code present.
Here is the guide I am following: Call Your API Using the Authorization Code Flow
Here is my server code:
const getAuth0Tokens = async(code)=>{
console.log(`code => here ${code}`)
var options = {
method: 'POST',
url: 'https://********.us.auth0.com/oauth/token',
headers: {'content-type': 'application/x-www-form-urlencoded'},
data: new URLSearchParams({
client_id: '*************clientId**********',
client_secret: '*************clientSecret**********',
audience: 'https://localhost:3000/login.html',
grant_type: 'authorization_code',
redirect_uri:"https://localhost:3000/login.html",
code:`${code}`
})
};
return await axios.request(options).then(function (response) {
console.log("data from auth0 token call " + JSON.stringify(response.data));
const {id_token,access_token, refresh_token, token_type, expires_in} = response.data;
return {id_token, access_token, refresh_token, token_type, expires_in}
}).catch(function (error) {
console.error(error);
});
Here is the response:
The request is returning successfully with 200 status response. For more context I am on the free subscription account tier.
Could the error be due to mu auth0 account configuration? or maybe something else.
You need to specify the scope with offline_access in order to retrieve the refresh_token. It is also mentioned in the same guide which you have linked above
Include offline_access to get a refresh token (make sure that the Allow Offline Access field is enabled in the Application Settings).
So you can just add new parameter scope: offline_access along with your other params
It looks like your scope is empty. You might need to add openid and profile to the scope param to get the id token.
It would help if you could show your authorize request too (step 1 in that guide).
Then, ya, what Umakanth said about the refresh token. Need to add offline_access to the scope.
Include offline_access to get a refresh token (make sure that the Allow Offline Access field is enabled in the Application Settings).
Related
Desired Behaviour
I am setting up a Node.js web application using Azure AD B2C authentication and authorisation.
It is a confidential, server side, client (i.e. - not a Single Page Application).
The desired behaviour is:
Authenticate and authorise a user via login using Azure ADB2C
Based on a successful login, allow users to call routes in the Node web app
My specific question is at the bottom of this post, but is essentially:
Given that, if an accessToken is present in req.session, my
application will return the result from calling ANY endpoint I choose
(even if it is not related to any of the 'scopes' defined when
'exposing the api'), are the scopes that are defined when 'exposing an
API' essentially 'for information purposes only' - both for
application admins and end users? Or should they somehow be enforced in each relevant Express route handler?
Research
I have done extensive reading and video watching around the topic, including:
GitHub Repositories:
active-directory-b2c-msal-node-sign-in-sign-out-webapp
active-directory-b2c-javascript-nodejs-webapi
learn.microsoft.com articles:
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-overview
https://learn.microsoft.com/en-au/azure/active-directory-b2c/configure-a-sample-node-web-app
https://docs.microsoft.com/azure/active-directory-b2c/enable-authentication-in-node-web-app
https://docs.microsoft.com/azure/active-directory-b2c/configure-authentication-in-sample-node-web-app-with-api
https://docs.microsoft.com/azure/active-directory-b2c/enable-authentication-in-node-web-app-with-api
Videos:
Identity for Developers Playlist by Microsoft Security
OAuth 2.0 and OpenID Connect (in plain English) by OktaDev
What I've Tried
I have been able to implement a basic prototype consisting of:
Node.js web application (a confidential, server side, client, not a Single Page App)
Sign Up, Sign In and Edit Profile User flows
2 x Application Registrations in Azure - one for the Web App and one for the Web App API
I have 'exposed the API' in the Web App API registration, eg: :
https://my-tenant-name.onmicrosoft.com/my-api-uri-thing/tasks.read
https://my-tenant-name.onmicrosoft.com/my-api-uri-thing/tasks.write
I have 'added permissions' to the Web App registration (to access the Web App API registration), eg:
MY-APP-API (2)
tasks.read, Delegated, Admin Consent Required - Yes, Granted for MY-APP
tasks.write, Delegated, Admin Consent Required - Yes, Granted for MY-APP
Microsoft Graph (2)
offline_access, Delegated, Admin Consent Required - No, Granted for MY-APP
openid, Delegated, Admin Consent Required - No, Granted for MY-APP
So the workflow is as follows (based on this code):
User clicks on a '/signin' link
The relevant Express route handler passes through the required scopes to getAuthCode():
app.get('/signin',(req, res)=>{
//Initiate a Auth Code Flow >> for sign in
//Pass the api scopes as well so that you received both the IdToken and accessToken
getAuthCode(process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY,apiConfig.webApiScopes, APP_STATES.LOGIN, res);
});
Source
The value of the scopes parameter is:
['https://${process.env.TENANT_NAME}.onmicrosoft.com/my-api-uri-thing/tasks.read','https://${process.env.TENANT_NAME}.onmicrosoft.com/my-api-uri-thing/tasks.write']
Upon successful sign in, the user is redirected to /redirect
The relevant Express route handler passes through a tokenRequest object to get an accessToken which is then added to req.session:
app.get('/redirect',(req, res)=>{
if (req.query.state === APP_STATES.LOGIN) {
// prepare the request for calling the web API
tokenRequest.authority = process.env.SIGN_UP_SIGN_IN_POLICY_AUTHORITY;
tokenRequest.scopes = apiConfig.webApiScopes;
tokenRequest.code = req.query.code;
confidentialClientApplication.acquireTokenByCode(tokenRequest)
.then((response) => {
req.session.accessToken = response.accessToken;
req.session.givenName = response.idTokenClaims.given_name;
console.log('\nAccessToken:' + req.session.accessToken);
res.render('signin', {showSignInButton: false, givenName: response.idTokenClaims.given_name});
}).catch((error) => {
console.log(error);
res.status(500).send(error);
});
}else{
res.status(500).send('We do not recognize this response!');
}
});
User calls a 'protected API'
The relevant Express route handler checks if req.session contains an accessToken value
If the accessToken is present, it makes an http request to the desired endpoint using axios and passes through the accessToken as the Bearer token in the headers of the request.
The 'protected content' is then returned
app.get('/api', async (req, res) => {
if(!req.session.accessToken){
//User is not logged in and so they can only call the anonymous API
try {
const response = await axios.get(apiConfig.anonymousUri);
console.log('API response' + response.data);
res.render('api',{data: JSON.stringify(response.data), showSignInButton: true, bg_color:'warning'});
} catch (error) {
console.error(error);
res.status(500).send(error);
}
}else{
//Users have the accessToken because they signed in and the accessToken is still in the session
console.log('\nAccessToken:' + req.session.accessToken);
let accessToken = req.session.accessToken;
const options = {
headers: {
//accessToken used as bearer token to call a protected API
Authorization: `Bearer ${accessToken}`
}
};
try {
const response = await axios.get(apiConfig.protectedUri, options);
console.log('API response' + response.data);
res.render('api',{data: JSON.stringify(response.data), showSignInButton: false, bg_color:'success', givenName: req.session.givenName});
} catch (error) {
console.error(error);
res.status(500).send(error);
}
}
});
Question
Given that, if an accessToken is present in req.session, my application will return the result from calling ANY endpoint I choose (even if it is not related to any of the 'scopes' defined when 'exposing the api'), are the scopes that are defined when 'exposing an API' essentially 'for information purposes only' - both for application admins and end users?
Or should I be coding in a conditional statement in each relevant Express route handler that says:
IF the required scope for accessing this content is present in this access token,
THEN you can access this content
and therefore 'enforces' the scope that has been defined and consented to by the user.
With my current level of understanding, I am assuming that these scopes ARE just for information purposes only, because I haven't seen any examples where the scopes are enforced through code.
I tried to get access_token and refresh_token using authorization code flow using node oidc provider. I got auth_code. but I could not get access token and refresh token How to fix this Issue. I referred many documentation but I could not get it.
OIDC Configuration
const oidc = new Provider('http://localhost:3000', {
clients: [
{
client_id: 'foo',
client_secret: 'bar',
redirect_uris: ['https://jwt.io'], // using jwt.io as redirect_uri to show the ID Token contents
response_types: ['code'],
grant_types: ['authorization_code'],
token_endpoint_auth_method: 'none',
},
],
cookies: {
keys: 'secretkey'
},
pkce: {
required: true
},
});
// Heroku has a proxy in front that terminates ssl, you should trust the proxy.
oidc.proxy = true;
app.use(oidc.callback())
I got auth_code also
How to get access token and refresh token using node-oidc provider
Your access token request is missing the PKCE code_verifier parameter.
your client's authentication method is set to none, so you're not supposed to pass any authorization header.
you can start your provider process with DEBUG=oidc-provider:* to get more details for these errors.
Invalid Client but you have input "client_id", it mean you are enabling features:
{
clientCredentials: {
enabled: true
}
}
So you must provide client_secret
and in oidc-provider source I see it always check code_verifier so you should provide it
In example project provided by Microsoft here which uses Authorization code flow the acquireTokenByCode method does not return refresh tokens.
From #azure/msal-node here refresh token is not mentioned.
Result returned from the authority's token endpoint.
uniqueId - oid or sub claim from ID token
tenantId - tid claim from ID token
scopes - Scopes that are validated for the respective token
account - An account object representation of the currently signed-in user
idToken - Id token received as part of the response
idTokenClaims - MSAL-relevant ID token claims
accessToken - Access token received as part of the response
fromCache - Boolean denoting whether token came from cache
expiresOn - Javascript Date object representing relative expiration of access token
extExpiresOn - Javascript Date object representing extended relative expiration of access token in case of server outage
state - Value passed in by user in request
familyId - Family ID identifier, usually only used for refresh tokens
please ensure your MSAL authorization code request includes the offline_access scope.
You could use MSAL.js to get token in this case, there is acquireTokenSilent method, it can perform silent renewal of tokens, which means you are no need to get the refresh token by yourself.
Popup
var request = {
scopes: ["Mail.Read"]
};
msalInstance.acquireTokenSilent(request).then(tokenResponse => {
// Do something with the tokenResponse
}).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenPopup(request);
}
}).catch(error => {
handleError(error);
});
Redirect
var request = {
scopes: ["Mail.Read"]
};
msalInstance.acquireTokenSilent(request).then(tokenResponse => {
// Do something with the tokenResponse
}).catch(error => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request)
}
});
It's designed to not return the refresh token if you are using #azure/msal-node.
As they stated in the discussion, the refresh token is handled background, inside the library itself for better security, which I also disagree with.
However, if you insist to have the token, you can manually call the API to the AzureAD endpoint.
I am developing an Angular 10 app that utilizes Azure B2C for policy and user management. I set up my app registration in Azure Active Directory as a singlepage app without the implicit option checked. I am using msal.js 2.0 #azure/msal-browser to log into B2C and retrieve id and access tokens using code flow. I set up my configuration, created the msal object, defined the redirect promise, then later call loginRedirect with the appropriate user scopes. The page redirects properly.
However, after I sign in the tokenResponse comes back as null. I have tried altering the authority and scopes, but it always comes back as null. How do I get the handleRedirectPromise to return a valid token response?
Here's my code:
private msalConfig: Msal.Configuration = {
auth: {
clientId: xxxx-xx-xx-xx-xxxxx,
authority: 'https://login.microsoftonline.com/common',
redirectUri: 'https://localhost:4200'
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: false
},
};
private loginRequest: Msal.RedirectRequest = {
scopes: ['user.read'],
};
const msalInstance = new Msal.PublicClientApplication(this.msalConfig);
msalInstance
.handleRedirectPromise()
.then((tokenResponse: Msal.AuthenticationResult) => {
let accountObj = null;
if (tokenResponse !== null) {
accountObj = tokenResponse.account;
const id_token = tokenResponse.idToken;
const access_token = tokenResponse.accessToken;
console.log('id_token', id_token);
console.log('access_token', access_token);
}
})
.catch(error => {
authStore.loginError$.next(true);
console.error(error);
});
msalInstance.loginRedirect(this.loginRequest);
Edit:
I have also tried authority: `https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/<policy-name> and https://login.microsoftonline.com/tfp/{tenant}.onmicrosoft.com/B2C_1_SiupIn for the authority in the msalConfig object as well as scopes: ['openid'] in the loginRequest. When I use this I get the following error in the browser when I try to log in:
zone-evergreen.js:1068 GET https://login.microsoftonline.com/common/discovery/instance?api-version=1.1&authorization_endpoint=https://<tenant>.b2clogin.com/<tenant>.onmicrosoft.com/b2c_1_defaultsigninsignup/oauth2/v2.0/authorize 400 (Bad Request)
core.js:4197 ERROR Error: Uncaught (in promise): ClientAuthError: endpoints_resolution_error: Error: could not resolve endpoints. Please check network and try again. Detail: ClientConfigurationError: untrusted_authority: The provided authority is not a trusted authority. Please include this authority in the knownAuthorities config parameter.
ClientAuthError: endpoints_resolution_error: Error: could not resolve endpoints. Please check network and try again. Detail: ClientConfigurationError: untrusted_authority: The provided authority is not a trusted authority. Please include this authority in the knownAuthorities config parameter.
The way you set up the redirect flow seems correct. You first have to call the handleRedirectPromise() (which registers it), and then call the loginRedirect(). At page load handleRedirectPromise() will return null, and after sign-in it should return the token.
There are issues with your configuration, however.
You need to designate your domain as a knownAuthority, like:
auth: {
clientId: 'xxxx-xx-xx-xx-xxxxx',
authority: 'https://<tenant-name>.b2clogin.com/<tenant-name>.onmicrosoft.com/<policy-name>',
knownAuthorities: ['<your-tenant-name>.b2clogin.com']
redirectUri: 'https://localhost:4200'
},
User.Read is a MS Graph API scope. You cannot use it with B2C. Only the OIDC scopes are allowed i.e. use openid instead.
See this for more.
The problem was with my angular app. I had my app redirecting the base url to my /home route. So whenever you open the base route the app is redirected. From this route, the request is made. I added the redirect uri for the /home route to my AAD app registration, commented out the redirectUri in my b2c configuration and set navigateToLoginRequestUrl to true.
I am developing a node.js application which uses outlook rest API to fetch the mails. I am using this API.
I am trying to refresh the token using the following request. I am using request npm to call the API
{
url: 'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token',
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
formData:
{
scope: 'offline_access User.Read Mail.Read',
client_id: 'c251b61b-c6db-4f64-89bd-7009444d1bc8',
grant_type: 'refresh_token',
redirect_uri: 'http://localhost:3000/myurl',
refresh_token: 'refresh-token',
client_secret: 'cli-secret'
}
}
but getting the following error
{
"error": "invalid_grant",
"error_description": "AADSTS9002313: Invalid request. Request is malformed or invalid.745ec0500",
"correlation_id": "a2d87f11-0671-41f1-a5e7-654f1796c3d1"
}
I have also tried with adding Content-length in headers and appending all variables into a string using & and = and sending that in the body, but I got the same error. I am getting an access-token successfully.
So far I know you are trying to get refresh token in wrong way!
As the error said ,you are trying in incorrect grant_type.
As per your given document reference the grant_type should be authorization_code. Once you would get your Code then you need to use it for achieving access tokenand refresh token.
When your access token would expired then you have to Use the refresh token to get a new access token as document explains
In that case try with response_type=code format. I hope it would resolve your problem.
Request For Code:
Get Code In Postman Console:
Request For Access And Refresh Token With Code:
Get Access And Refresh Token By Code:
Get Refresh Token When Access Token Expired:
Note: This this the exact way how you would get authorization code and with this code how to get access token and refresh
token finally how to renew token with the refresh token when the
access token expired!
Thank you and happy coding!
When you generate the access token the first time that time you also get the refresh token. you have to store that token anywhere you can also store it in a database or a txt file.
$post_params_refresh = array(
"grant_type" => "refresh_token",
"client_id" => 'ReplaceYourClientId',
"refresh_token" => 'ReplaceYourOldRefreshToken',
"client_secret" => 'ReplaceYourClientSecretKey',
'scope' => 'https://graph.microsoft.com/User.ReadWrite.All',
);
$refreshTokenUrl = "https://login.windows.net/common/oauth2/v2.0/token";
$curl_refresh = curl_init($refreshTokenUrl);
curl_setopt($curl_refresh, CURLOPT_POST, true);
curl_setopt($curl_refresh, CURLOPT_POSTFIELDS, $post_params_refresh);
curl_setopt($curl_refresh, CURLOPT_HTTPHEADER, array("application/x-www-form-urlencoded"));
curl_setopt($curl_refresh, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl_refresh, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl_refresh, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl_refresh, CURLOPT_RETURNTRANSFER, 1);
$response_refresh = curl_exec($curl_refresh);
$arrResponseRefresh = json_decode($response_refresh);
$accessToken = $arrResponseRefresh->access_token;
$refreshToken = $arrResponseRefresh->refresh_token;
You get a new refresh token using this curl method and update this refresh token to the old token which you stored previously.