Client has not been granted scopes: read roles - javascript

Using the node-auth0 package, had connections working properly, but am now attempting to do some work with roles and have added the read:roles scope, which now results in an error.
Minimal code below:
const ManagementClient = require('auth0').ManagementClient;
const auth0 = new ManagementClient(
{
domain: process.env.AUTH0_DOMAIN,
clientId: process.env.AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
scope: 'read:users update:users read:roles'
});
// both these calls fail
const auth0_roles = auth0.roles.getAll();
const auth0_tester = auth0.users.create(data);
Now the calls to EITHER roles or users fail with this error:
{"error":"access_denied","error_description":"Client has not been granted scopes: read:roles"}
If I remove read:roles from the scope, the create user call succeeds (but obviously the roles.getAll fails)
I've checked my API permissions in the dashboard (APIs => Auth0 Management API => Permissions), and it appears to include everything:
(NOTE: there's a message at the top of the permissions that indicates Important: Permission Management is not available for APIs representing Auth0 Resources. - and I am NOT able to change permissions )

Per my comment to Shayan's excellent answer, I'd actually been in that area of the Auth0 dashboard many times and had missed the magic link.
The image below shows where you have to click in order to set the client grants you want the application to have.

You cannot change add/edit/remove permissions for your Management API resource because as the dashboard message says: It is an Auth0 resource server with the identifier/audience as https://YOUR_DOMAIN.REGION.auth0.com/api/v2/. The scopes are managed by Auth0. You can however modify what scopes are granted to your Server-side Clients i.e Machine-to-Machine or Web Application client types, and limit what they can request when using Client Credentials grant to request an Access Token for an API.
In the same section under Management API resource settings, besides the "Permissions" tab, you can open "Machine to Machine Applications" tab, find your clientID and make sure it is authorized to request the scopes it requires for that API resource (eg. the read:roles scope). These are called Client Grants and you can manage them from Dashboard or Management API: https://auth0.com/docs/api/management/v2#!/Client_Grants/get_client_grants
Auth0 has a Client Credential Hook you can utilize for more specific logic in your flow, read more here: https://auth0.com/docs/api-auth/tutorials/client-credentials/customize-with-hooks

Related

auth0 access token doesn't show issuer details

We recently added auth0 for integrating SSO from different oauth2 providers (e.g. contoso1.auth.com and contoso2.auth.com)
https://auth0.com/docs/quickstart/spa/angular/01-login
I followed the above link and Our front end app successfully integrated this in the code and able to signin and get the token.
{
"iss": "https://TENANT_NAME.auth0.com/",
"sub": "auth0|SOME_HASH",
"aud": [
"https://API_IDENTIFIER",
"https://TENANT_NAME.auth0.com/userinfo"
],
"iat": 1563699940,
"exp": 1563786340,
"azp": "SOME_OTHER_HASH",
"scope": "openid profile email"
}
In our angular app we want to render ui (show or hide links based on which authentication(contoso1/contoso2) user has gone through. But auth0 accesstoken doesn't give any details about the issuer "iss" (e.g.contoso1.auth.com or contoso2.auth.com)
We cannot rely on the email to say which SSO user belongs to as in our case contoso1 and contoso2 can have users from each others system with their own email ids.
After spending sometime on auth0 page i realized we have a field "connection" in the datacontext of auth0 object and it stores the name . While we can use this as a temporary workaround we can't rely on this determine which SSO flow user signed in with.
{
tenant: "identity-dev"
clientID: "fdsfsdf-dfsdfsd8989",
clientName: "Angualr Portal",
clientMetadata: "{}"
connection : "contoso1-backchannel",
connectionStrategy:"oidc"
....more
}
Please let me know how we can fetch iss or issuer url details in the token.
Is it a requirement to get this info using the frontend only?
As per this Auth0 article, it is a bit easier if you have a backend in place:
If your code runs in the backend, then we can assume that your server is trusted to safely store secrets (as you will see, we use a secret in the backend scenario).
With the backend you will be able to retrieve and parse the identities array user.identities[i].provider, which clearly identifies the original issuer under provider and connection keys.
If using only a frontend, it is more work and you need to build a proxy:
When working with a frontend app, the process for calling IdP APIs differs from the backend process because frontend apps are public applications that cannot hold credentials securely. Because SPA code can be viewed and altered, and native/mobile apps can be decompiled and inspected, they cannot be trusted to hold sensitive information like secret keys or passwords.
The quoted article contains links in the "Show me how" box that might be of further interest in this regard.
From your post it seems to be that only a frontend is used, but I included info about the backend in case it is worth your while to implement a small backend, if purely to just make retrieving the identity provider a bit easier.

Single flow: sign user in via Google oAuth AND grant offline/server access?

I'm trying to implement Google sign-in and API access for a web app with a Node.js back end. Google's docs provide two options using a combo of platform.js client-side and google-auth-library server-side:
Google Sign-In with back-end auth, via which users can log into my app using their Google account. (auth2.signIn() on the client and verifyIdToken() on the server.)
Google Sign-in for server-side apps, via which I can authorize the server to connect to Google directly on behalf of my users. (auth2.grantOfflineAccess() on the client, which returns a code I can pass to getToken() on the server.)
I need both: I want to authenticate users via Google sign-in; and, I want to set up server auth so it can also work on behalf of the user.
I can't figure out how to do this with a single authentication flow. The closest I can get is to do the two in sequence: authenticate the user first with signIn(), and then (as needed), do a second pass via grantOfflineAccess(). This is problematic:
The user now has to go through two authentications back to back, which is awkward and makes it look like there's something broken with my app.
In order to avoid running afoul of popup blockers, I can't give them those two flows on top of each other; I have to do the first authentication, then supply a button to start the second authentication. This is super-awkward because now I have to explain why the first one wasn't enough.
Ideally there's some variant of signIn() that adds the offline access into the initial authentication flow and returns the code along with the usual tokens, but I'm not seeing anything. Help?
(Edit: Some advice I received elsewhere is to implement only flow #2, then use a secure cookie store some sort of user identifier that I check against the user account with each request. I can see that this would work functionally, but it basically means I'm rolling my own login system, which would seem to increase the chance I introduce bugs in a critical system.)
To add an API to an existing Google Sign-In integration the best option is to implement incremental authorization. For this, you need to use both google-auth-library and googleapis, so that users can have this workflow:
Authenticate with Google Sign-In.
Authorize your application to use their information to integrate it with a Google API. For instance, Google Calendar. 
For this, your client-side JavaScript for authentication might require some changes to request
offline access:
$('#signinButton').click(function() {
auth2.grantOfflineAccess().then(signInCallback);
});
In the response, you will have a JSON object with an authorization code:
{"code":"4/yU4cQZTMnnMtetyFcIWNItG32eKxxxgXXX-Z4yyJJJo.4qHskT-UtugceFc0ZRONyF4z7U4UmAI"}
After this, you can use the one-time code to exchange it for an access token and refresh token.
Here are some workflow details:
The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token after the user has been presented an authorization dialog requesting offline access. If you've specified the select-account prompt in the OfflineAccessOptions [...], you must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token
Therefore, you should use google-auth-library to complete this workflow in the back-end. For this,
you'll use the authentication code to get a refresh token. However, as this is an offline workflow,
you also need to verify the integrity of the provided code as the documentation explains:
If you use Google Sign-In with an app or site that communicates with a backend server, you might need to identify the currently signed-in user on the server. To do so securely, after a user successfully signs in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity of the ID token and use the user information contained in the token
The final function to get the refresh token that you should persist in your database might look like
this:
const { OAuth2Client } = require('google-auth-library');
/**
* Create a new OAuth2Client, and go through the OAuth2 content
* workflow. Return the refresh token.
*/
function getRefreshToken(code, scope) {
return new Promise((resolve, reject) => {
// Create an oAuth client to authorize the API call. Secrets should be
// downloaded from the Google Developers Console.
const oAuth2Client = new OAuth2Client(
YOUR_CLIENT_ID,
YOUR_CLIENT_SECRET,
YOUR_REDIRECT_URL
);
// Generate the url that will be used for the consent dialog.
await oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope,
});
// Verify the integrity of the idToken through the authentication
// code and use the user information contained in the token
const { tokens } = await client.getToken(code);
const ticket = await client.verifyIdToken({
idToken: tokens.id_token!,
audience: keys.web.client_secret,
});
idInfo = ticket.getPayload();
return tokens.refresh_token;
})
}
At this point, we've refactored the authentication workflow to support Google APIs. However, you haven't asked the user to authorize it yet. Since you also need to grant offline access, you should request additional permissions through your client-side application. Keep in mind that you already need an active session.
const googleOauth = gapi.auth2.getAuthInstance();
const newScope = "https://www.googleapis.com/auth/calendar"
googleOauth = auth2.currentUser.get();
googleOauth.grantOfflineAccess({ scope: newScope }).then(
function(success){
console.log(JSON.stringify({ message: "success", value: success }));
},
function(fail){
alert(JSON.stringify({message: "fail", value: fail}));
});
You're done with the front-end changes and you're only missing one step. To create a Google API's client in the back-end with the googleapis library, you need to use the refresh token from the previous step.
For a complete workflow with a Node.js back-end, you might find my gist helpful.
While authentication (sign in), you need to add "offline" access type (by default online) , so you will get a refresh token which you can use to get access token later without further user consent/authentication. You don't need to grant offline later, but only during signing in by adding the offline access_type. I don't know about platform.js but used "passport" npm module . I have also used "googleapis" npm module/library, this is official by Google.
https://developers.google.com/identity/protocols/oauth2/web-server
https://github.com/googleapis/google-api-nodejs-client
Check this:
https://github.com/googleapis/google-api-nodejs-client#generating-an-authentication-url
EDIT: You have a server side & you need to work on behalf of the user. You also want to use Google for signing in. You just need #2 Google Sign-in for server-side apps , why are you considering both #1 & #2 options.
I can think of #2 as the proper way based on your requirements. If you just want to signin, use basic scope such as email & profile (openid connect) to identify the user. And if you want user delegated permission (such as you want to automatically create an event in users calendar), just add the offline access_type during sign in. You can use only signing in for registered users & offline_access for new users.
Above is a single authentication flow.

How does the consent flow determine which resources to ask for?

I have a multi-tenant service principal that exposes a custom API. Using MSAL.js' UserAgentApplication I'm able to ask for consent for the resources I need on first-time use with loginPopup. However, I'm confused as to which resources to specify in the request. For instance, let's say I use the following popup (note the lack of scope):
await this.userAgentApplication.loginPopup({
prompt: 'consent',
authority: "https://login.microsoftonline.com/organizations"
})
The application will simply request the user's profile. Fair enough.
However, let's say I configure the popup as follows:
await this.userAgentApplication.loginPopup({
scopes: ["api://xyz/Some.Scope"]
prompt: "consent"
authority: "https://login.microsoftonline.com/organizations"
})
This causes an exception:
The user or administrator has not consented to use the application with ID 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx' named 'XYZ'. Send an interactive authorization request for this user and resource.
Why do I get this error even when I'm logging in using a Global Administrator account?
Lastly, in addition to our own API data we needed to be able to read Graph Groups in application context, so I requested these using the .default endpoint (permissions are specified in the Service Principal registration). I did this using the following popup:
await this.userAgentApplication.loginPopup({
scopes: ["https://graph.microsoft.com/.default"],
prompt: 'consent',
authority: "https://login.microsoftonline.com/organizations"
})
The result of the last attempt was ... all the permissions I was hoping for!
Sign in and read user profile
Read and write all groups
My Custom API scope (Application name)
But why does our Graph consent request automatically include requests for other custom scopes?
It is the expected behaviour if you use admin consent for the resources.
When you set ***/.default as the scope, it is equivalent to executing "Grant admin consent for {your tenant}" in Azure portal.
So it will asks admin consent for all the required permissions no matter whether they are from the required resource or not.
But if you set https://graph.microsoft.com/user.read, it will ask you to do consent only for user.read permission.
So in this case, once you use the last one to do the admin consent, api://xyz/Some.Scope will also take effect.
I also have a test with my custom API api://***/.default and api://***/user.write and both work as expected.
You can also try to use the following request to do the admin consent:
https://login.microsoftonline.com/jmaster.onmicrosoft.com/oauth2/v2.0/authorize?
client_id={client id}
&response_type=code
&redirect_uri={redirect url}
&response_mode=query
&scope=api://xyz/Some.Scope
&state=12345
&prompt=consent
Please have a retry with api://***/.default. Don't worry if it doesn't work, because api://xyz/Some.Scope will also take effect using the last code snippet.

How to Get a valid access token for my API and Microsoft Graph from Azure Active Directory?

I am trying to set up a spa javascript app which logs the user into our azure active directory tenant and then retrieves profile information from microsoft graph and calls an azure function written in c# core (my API).
I have separate application registrations set up for my website and the api in azure active directory.
I'm using the MSAL.js library in the javascript spa website and I'm using the newer microsoft identity / v2.0 endpoints.
The SPA app signs into active directory as expected and is able to use the access token to make the call to graph for the profile information. In my azure function I validate the token and this fails with the error "IDX10511: Signature validation failed. Keys tried: ....."
Now if I remove Microsoft graph from the scopes when requesting a token I get a token that when passed to the azure function validates perfectly well but I can no longer retrieve profile data in the spa app?
How do I get both to work?
Its also worth noting that ive tested the tokens with jwt.io and it is also unable to verify the signature when graph is used.
Heres how I'm getting my token:
var msalConfig = {
auth: {
redirectUri: window.location.origin, // forces top level instead of specific login pages - fixes deep link issues.
clientId: "Client ID of the website app", //This is your client ID
authority:
"https://login.microsoftonline.com/my-tennant-guid" //This is your tenant info
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
const msalUserAgent = new Msal.UserAgentApplication(msalConfig);
var requestObj = {
scopes: ["User.Read", "api://MyApi/Access"]
};
//when the spa starts up I login using redirects
msalUserAgent.loginRedirect(requestObj);
//then before calling an api I request a token using this method
acquireTokenSilent() {
var promise = msalUserAgent.acquireTokenSilent(requestObj);
return promise;
},
Try specifying the scopes as scopes: ["User.Read"] in the acquireTokenSilent() function.
Since an access token is only valid for one API.
If you need two, call acquireTokenSilent twice with different scopes.
It's okay to specify scopes for two APIs when signing in, but not when getting tokens. A token has an audience that specifies the target API. So you can't use a token for one API against another. And that's why it's only valid for one API.

Microsoft Graph API 401 when accessing calendar events

I'm currently trying to create a basic website to run locally on a raspberry pi that shows me my outlook calendar for the day. However, when I try and access the https://graph.microsoft.com/v1.0/me/events endpoint, I get a 401 unauthorized error.
This is for a simple node.js express server, with all of the code for interacting with the graph api coming from the javascript quickstart app provided by Microsoft. The quickstart app works fine, and I'm able to see the json response for the https://graph.microsoft.com/v1.0/me endpoint that is part of the demo, but changing the endpoint causes a 401.
I've made sure in the azure app registration page that I've enabled the Calendar.read permission, and in my app I've added the calendar.read permission to my scope. When I login, the login popup window asks for permission to read my calendar which to me says that the app should have access to my calendar.
My application configuration (again based off of the quickstart app) looks like this:
const applicationConfig = {
clientID: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
graphScopes: ['calendars.read', 'tasks.read'],
graphEndpoint: 'https://graph.microsoft.com/v1.0/me/events',
};
Other than these changes, the rest of the code is identical to the sample code.
You should also include User.Read in your scopes. From the documentation on User.Read:
Allows users to sign-in to the app, and allows the app to read the profile of signed-in users. It also allows the app to read basic company information of signed-in users.
const applicationConfig = {
clientID: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
authority: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
graphScopes: ['Users.Read', 'Calendars.Read', 'Tasks.Read'],
graphEndpoint: 'https://graph.microsoft.com/v1.0/me/events',
};

Categories

Resources