I'm working with Power Bi REST APIs, for which I need an azure ad token.
I have set up an app in Azure AD, and have configured it as.
a. I intend to use that access token in my react app so I have configured it as SPA.
b. i have allowed public-client flow.
** I hv not checked 'access token' and 'id token' checkboxes as I'm using msal 2.0.
c. also have specified redirect uri as http://localhost:4200
d. also have given it all the permission I need to access my powerbi content (I need 'Dataset.ReadWrite.All)
THESE ARE MY AZURE APP ENDPOINTS
I HAVE SET MY REACT APP AS:
I'm using #azure/msal-browser and #azure/msal-react libraries.
This is my msalConfig object-
const configuration: Configuration = {
auth: {
clientId: "myclientidhere",//,
authority: "https://login.microsoftonline.com/mytenantidhere",
redirectUri: "http://localhost:4200/",
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: false,
},
}
and, this is my scope:
export const loginRequest = {
scopes: ["Dataset.ReadWrite.All"]
};
after i run my app i see the auth popup. i put my credentials in it. and suddenly it throws this error-
invalid_client: AADSTS650053: The application 'dashboard.xyz.work' asked for scope 'Dataset.ReadWrite.All' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor. Trace ID: 77e47883-fdd3-444a-bdd3-9f3a53bc1500 Correlation ID: aa77d724-0d9f-41aa-8e47-251c6b6f9293 Timestamp: 2023-02-09 13:51:46Z
i have granted the same permission in my azure ad app. however my app has not been granted 'admin consent' but as a user my account has the permission to use this scope in powerbi.
NOTE: if I change my scope to 'user.read' or any other ms graph API resource, then I'm able to get an access token of that scope to access graph API resource. but I'm not able to get an access token to access my powerbi resources.
EVERYTHING LOOKS GREAT IN AZURE AD.
I READ IT SOMEWHERE THAT THIS RESOURCE '00000003-0000-0000-c000-000000000000' indicates to the graph.microsoft.com resource. and I'm hitting https://login.microsoftonline.com/{myTenantId} . these are my app endpoints.
I'm not sure if powerbi resources come under graph.microsoft.com ('00000003-0000-0000-c000-000000000000) resource!!??
also on my app's API permissions page I read, they come under https://analysis.windows.net/powerbi/api that is 00000009-0000-0000-c000-000000000000??
am I hitting the wrong endpoint or the issue is something else??
I tried to reproduce the same in my environment and got the same error as below:
To resolve the error, try the below:
I created an Azure AD SPA Application and added API permissions:
Note: Make sure to give scope as
https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All to access PowerBI content
I generated the auth-code by using below endpoint:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/authorize?
client_id=ClientID
&response_type=code
&redirect_uri=https://jwt.ms
&response_mode=query
&scope=https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All
&state=12345
&code_challenge=codeChallenge
&code_challenge_method=S256
As admin consent is not granted to the API permissions, you will get the consent screen as below:
The auth-code got generated successfully without any error as below:
Now, I generated access token by using below parameters:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
grant_type:authorization_code
client_id:ClientID
scope:https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All
code:code
redirect_uri:https://jwt.ms
code_verifier:S256
The access token generated successfully with the scope Dataset.ReadWrite.All like below:
To resolve the error, modify the code as below:
export const loginRequest = {
scopes: ["https://analysis.windows.net/powerbi/api/Dataset.ReadWrite.All"]
};
If still the issue persists, try scope as https://analysis.windows.net/powerbi/api/.default.
Related
I am currently trying to implement a Cypress e2e test which is involving Azure B2C AD as external identity provider.
So first I tried to visit the login page just as a normal user would, with:
cy.get('#loginButton').click();
// this will visit:
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/oauth2/v2.0/authorize?...
cy.wait(2000);
cy.get('input').first().type(email).should('have.value', email);
cy.get('[id="password"]').type(password).should('have.value', password);
cy.get('#next').click();
// should now redirect me to my app with the access token in my local storage
But then i get the following error:
{statusCode: 401, message: "Unauthorized"}
While doing it myself inside the browser works perfectly fine. Cypress cannot do it. Then I learned, that Cypress cannot accept state from external websites and I need to get the login token with cy.request() and inject it in my application:
https://github.com/cypress-io/cypress/issues/1342
Tutorial: Azure AD Authentication in Cypress Tests with MSAL
Example Code
The Problem here is, that they used:
https://login.microsoftonline.com/${Cypress.env('AZURE_TENANT_ID')}/oauth2/v2.0/token
but my app uses:
https://${Cypress.env('AZURE_TENANT_NAME')}.b2clogin.com/${Cypress.env('AZURE_TENANT_NAME')}.onmicrosoft.com/${Cypress.env('AZURE_AD_LOGIN_POLICY_NAME')}/oauth2/v2.0/token
I tried to adapt the tutorial request, but it does not work for me:
Cypress.Commands.add('login', () => {
cy.request({
method: 'POST',
url: `https://${Cypress.env('AZURE_AD_AUTH_TENANT')}.b2clogin.com/${Cypress.env('AZURE_AD_AUTH_TENANT')}.onmicrosoft.com/${Cypress.env('AZURE_AD_AUTH_LOGIN_POLICY_NAME')}/oauth2/v2.0/Token`,
form: true,
body: {
grant_type: 'password',
client_id: `${Cypress.env('AZURE_AD_AUTH_CLIENT_ID')}`,
client_secret: Cypress.env('AZURE_AD_AUTH_CLIENT_SECRET'),
scope: 'openid%20offline_access',
username: Cypress.env('AZURE_USERNAME'),
password: Cypress.env('AZURE_PASSWORD')
},
}).then((response) => {
injectTokens(response);
});
This request yields:
{
"error": "invalid_request",
"error_description": "AADB2C90055: The scope 'openid offline_access' provided in request must specify a resource, such as 'https://example.com/calendar.read'.\r\nCorrelation ID: 147e543e-7b12-4349-8917-ad7d97b4b7cd\r\nTimestamp: 2022-12-08 11:40:05Z\r\n"
}
Sadly I have no clue what this error means, which URL need i to provide? Which is the permission a user has to login to the website? I just want the token so i can visit the website as authentificated user with Cypress.
I know here are some posts with suggestions on how to approach this, but no a single one works for my url, with the ".b2clogin.com" inside, this works diffrent i guess.
Here are the 3 exposed urls i can call:
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/oauth2/v2.0/authorize
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{loginPolicy}/SelfAsserted
https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{loginPolicy}/oauth2/v2.0/token
I tried to reproduce the same in my environment via Postman and got below results:
I registered one web application in my B2C tenant and added API permissions as below:
In Manifest, make sure to enable implicit flow like below:
Now, I created one resource owner user flow like below:
When I tried to get the token giving same parameters via Postman, I got same error as you like below:
POST https://b2ctenantname.b2clogin.com/b2ctenantname.onmicrosoft.com/<ROPC_policyname>/oauth2/v2.0/token
grant_type:password
client_id:<App_ID>
client_secret:<secret>
scope:openid offline_access
username:<UPN_of_B2C user>
password:xxxxxxxxxxxx
Response:
To resolve the error, you need to include your Application ID in scope parameter.
I generated the tokens successfully by changing scope like below:
POST https://b2ctenantname.b2clogin.com/b2ctenantname.onmicrosoft.com/<ROPC_policyname>/oauth2/v2.0/token
grant_type:password
client_id:<App_ID>
client_secret:<secret>
scope:openid <App_ID> offline_access
username:<UPN_of_B2C user>
password:xxxxxxxxxxxx
Response:
If you want to get both access and id tokens, then include response_type parameter like below:
POST https://b2ctenantname.b2clogin.com/b2ctenantname.onmicrosoft.com/<ROPC_policyname>/oauth2/v2.0/token
grant_type:password
client_id:<App_ID>
client_secret:<secret>
scope:openid <App_ID> offline_access
response_type: token id_token
username:<UPN_of_B2C user>
password:xxxxxxxxxxxx
Response:
In your scenario, change value of scope parameter in your code by including your Application ID along with openid and offline_access.
Reference:
Set up a resource owner password credentials flow - Azure AD B2C | Microsoft
I have a web application registered in Azure AD and have it working with the Graph API. But I would like to be able to instead query the Sharepoint REST API.
I have added the sharepoint delegated permission scope "AllSites.Read" to my application (in addition to the Graph API scopes that I was using before) and request this scope (in addition to the other delagated msgraph scopes) when I get the oauth token from the user. I successfully get the token, using https://login.microsoftonline.com/common/oauth2/v2.0 for the authorization/token calls, but am unable to make a successful query:
My query looks like client.get(f"https://{tenant}.sharepoint.com/_api/web/lists") where tenant is the tenant of the particular user who's token I am using.
The error I get looks like {'error_description': 'Invalid issuer or signature.'} with reason="Token contains invalid signature.";category="invalid_client"' in the header of the response.
I am able to query the Graph api, but would like to also be able to query the Sharepoint REST api, because the Graph api is is insufficient for my actual use case, which will be to get Sharepoint groups (Graph api does not give sharepoint groups when I ask for groups, only Office 365 and Azure AD groups).
Update:
The permissions I've set on the app:
I have not added any scopes in Expose API, I don't know if I need to. I did not need this part to have it working with Graph API.
Lastly I'll mention that in Postman, controlled environment purely with this as the request, with OAuth 2.0:
Auth URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Access Token URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
client_id
client_secret
Scope: AllSites.Read
I get a token successfully, with all the roles, although it still doesn't give me access to https://<tenant>.sharepoint.com/_api/web/lists. I get the following error:
"error": {
"code": "-2147024891, System.UnauthorizedAccessException",
"message": {
"lang": "en-US",
"value": "Access denied. You do not have permission to perform this action or access this resource."
}
}
}
which admittedly is probably a step forward from the invalid client error I was getting before, but still quite stuck.
I was able to get this to work in Postman:
OAuth 2.0
Grant Type: Authorization Code
Auth URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Access Token URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
Client ID: <client_id>
Client Secret: <client_secret>
Scope: https://<tenant>.sharepoint.com/AllSites.FullControl
The token I get back has all of the permissions that I set on the application, including the Graph API ones and the Sharepoint scopes that I did not request in the Scope parameter of the auth request:
"scp": "AllSites.FullControl AllSites.Read Directory.Read.All Files.Read.All Group.Read.All MyFiles.Read Sites.Read.All Sites.Search.All User.Read User.Read.All", which was a little surprising.
A key point was setting the tenant url in the scope so that the aud parameter in the token comes back for the right tenant. It was coming back before configured for the resourceAppId associated with the Graph permissions (00000003-0000-0000-c000-000000000000), rather than the Sharepoint permissions. This way, aud got set to https://<tenant>.sharepoint.com and I was able to access https://<tenant>.sharepoint.com/_api/web/lists.
You can try to get the acccess token in PostMan for a testing purpose.
Callback URL: https://www.getpostman.com/oauth2/callback
Auth URL : https://login.microsoftonline.com/common/oauth2/authorize?resource=https://<tenant_name>.sharepoint.com
Access Token URL : https://login.microsoftonline.com/common/oauth2/token
Client ID : <Application_ID>
Client Secret : <KEY>
Grant Type : Authorization Code
Will pop up a login window to sign in and then generate the access token and get the SharePoint groups:
Reference:
Use Postman and Azure AD to send REST request to SharePoint Online
I have an Azure Function setup with a Web Trigger endpoint that I want to use as my backend for a React app. Without authentication setup, it works fine. When I setup App Service Authentication using AD, it works fine when I access directly via the browser (after authentication), but when I try to access from JS providing the Bearer token I get a 401.
const response = await axios.get(`${window.apiUrl}api/jobs`, {
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token.accessToken,
},
});
The client app is running on Azure and is registered as an Azure AD app. I am able to authenticate, query AD, and use MS Graph API successfully.
I am using the built-in Azure App Services AD authentication. I have the Client ID set as the same client ID as the previously mentioned Azure AD app, as well as the same Issuer Url.
Attempt to get session token:
const accessToken = await authProvider.getAccessToken();
const idToken = await authProvider.getIdToken();
const login = await axios.post(
'https://<appname>.azurewebsites.net/.auth/login/aad',
{ access_token: accessToken.accessToken },
{
headers: {
'Content-Type': 'application/json',
},
},
);
More Info
My aud claim is 00000003-0000-0000-c000-000000000000. In Azure Portal, my Azure Function is configured to use the same Azure AD App as my SPA. I am using MSAL.js for authentication in my SPA. I am requesting the User.Read and Directory.Read.All scopes.
Microsoft has published a how-to article entitled Advanced usage of authentication and authorization in Azure App Service. In the section on validating tokens from providers, it says:
In a client-directed sign-in, the application signs in the user to the
provider manually and then submits the authentication token to App
Service for validation (see Authentication flow). This validation
itself doesn't actually grant you access to the desired app resources,
but a successful validation will give you a session token that you can
use to access app resources.
So you need to get the session token to access app resources.
Request:
POST https://<appname>.azurewebsites.net/.auth/login/aad HTTP/1.1
Content-Type: application/json
{"id_token":"<token>","access_token":"<token>"}
Response:
{
"authenticationToken": "...",
"user": {
"userId": "sid:..."
}
}
Once you have this session token(authenticationToken), you can access protected app resources by adding the X-ZUMO-AUTH header to your HTTP requests
GET https://<appname>.azurewebsites.net/api/products/1
X-ZUMO-AUTH: <authenticationToken_value>
You cannot request a token for Microsoft Graph and use it to call your own API. The Audience "00000003-0000-0000-c000-000000000000" means "intended for Microsoft Graph".
In MSAL, when you request the token, you need to adjust the scopes. Delete User.Read, delete Directory.Read.All and add the "Application ID URI" with a /.default at the end of it. You can find the Application ID URI in the "Expose an API" blade of your application registration on portal.azure.com. Example: https://SaeedApp/.default
If you need to do both, you can only request an access token for one resource at a time. However, you can request as many scopes as you need for one resource (User.Read and Directory.Read.All are both scopes for the same resource).
So you'll need to make two sets of requests:
1) to get an access token with all the scopes you need for Microsoft Graph
2) to get an access token with all of the scopes you need for your API
The reason behind why: If I could take an access token that's intended for your API and call Microsoft Graph with it, then that would open up "replay" attacks where one Resource API is hacked and the hacker that controls one resource can now reply access tokens it receives from clients against all the other Resource APIs.
We are testing locking down an API hosted on Azure Web Apps by using the built-in Azure Web App Authentication/Authorization with Azure Active Directory.
We are calling POST https://login.microsoftonline.com/{tenantIDhere}/oauth2/v2.0/token with the following parameters in Postman:
grant_type: password
client_id: {ID}
username: {username}#{tenenat}.onmicrosoft.com
password: {password}
scope: https://{webappname}.azurewebsites.net/.default
client_secret: {secret}
Below is the response I get:
"token_type":"Bearer","scope":"https://{webappname}.azurewebsites.net/user_impersonation https://{webappname}.azurewebsites.net/.default","expires_in":3599,"ext_expires_in":3599,"access_token":"{TOKEN HERE}"}
So I take that token and try and access the webpage by passing the token in the header and I get the below error using POST:
https://{webappname}.azurewebsites.net/api/url
Authorization: Bearer {TOKEN HERE}
Response (401 Unauthorized)
You do not have permission to view this directory or page.
I have spent days trying everything I can find. What am I missing?? As soon as I turn off authorization needed for the web app, I get the expected result. But using the grant_type: password, I CANNOT get in! I have a feeling its related to the scope, but I cannot find documentation on what I need here for this. Any ideas?
The user is able to manually login to the webpage and pull the data, so it is not a permission issue. It has something to do with the token I believe.
If you are using v2.0 endpoint, the scope should be {your_client_id}/.default. Replace https://{webappname}.azurewebsites.net with the client id/application id for your API app in Azure AD.
Edit:
As Wayne said, you could also set scope as {your_client_id}/User.Read to get access token.
I have registered a multitenant app at https://apps.dev.microsoft.com since the "admin consent" prompt wasn't available in the Azure AD apps. Admin consent is required for our app to retrieve info about users and their calendars.
I can provide admin consent from a completely different tenant than what this app is registered from and use the provided access token to retrieve all necessary information, however that obviously expires after an hour and we need offline access.
I have tried using the tenantId instead of 'common' in the https://login.windows.net/common/oauth2/token endpoint, however receive the same message as below.
The following is the data being submitted to the token endpoint in json format (converted within node to form encoded format before submitting):
{
grant_type: 'refresh_token',
client_id: 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3',
client_secret: '(redacted)',
refresh_token: '(redacted)',
scope: 'openid offline_access calendars.read user.read.all'
}
When I try to refresh the access token I receive an error:
{
"error":"invalid_grant",
"error_description":"AADSTS65001: The user or administrator has not consented to use the application with ID 'e5c0d59d-b2c8-4916-99ac-3c06d942b3e3'. Send an interactive authorization request for this user and resource.\r\nTrace ID: 2bffaa08-8c56-4872-8f9c-985417402e00\r\nCorrelation ID: c7653601-bf96-46c3-b1ff-4857fb25b7dc\r\nTimestamp: 2017-03-22 02:17:13Z",
"error_codes":[65001],
"timestamp":"2017-03-22 02:17:13Z",
"trace_id":"2bffaa08-8c56-4872-8f9c-985417402e00",
"correlation_id":"c7653601-bf96-46c3-b1ff-4857fb25b7dc"
}
This error occurs even when standard consent is used. I have also tried using the node-adal library instead of raw http requests which produces the exact same result.
I note that "offline_access" isn't a permission I am able to set within the MS apps portal, however I would guess the fact that I am getting a refresh token back means that I can refresh the access token?
For the record, the following is the node-adal code I used to see if I was doing something wrong:
var self = this;
var authenticationContext = new AuthenticationContext('https://login.windows.net/common');
authenticationContext.acquireTokenWithRefreshToken(
self.refreshToken,
self.clientId,
self.clientSecret,
'https://graph.microsoft.com/',
function(a) {
console.log(a);
}
);
Any help in getting this refresh process working is appreciated!
Please ensure that the tenant that you using for refreshing token is same as the tenant that you requesting for the access_token.
The refresh token request works well for me unless in the scenario of below:
register the app from protal using Microsoft account
user1 is in tenant1
add user1 as the external users to tenant2
request the access_token/refresh_token from tenant1(OK)
try to refresh the token using tenant1 in the request(OK)
try to refresh the token using tenant2 in the request(same error message)