How to use ADAL.js and enable a secure CORS call to Web API and Azure AD - azure

I have a single page application needs to call a CORS enable web api. Both applications are secured by AAD. I found a sample done by Mat Velloso at https://github.com/matvelloso/AngularJSCORS
I've followed the steps in the readme file, only thing not sure was "remember to use the class ID from the client application you created" in step 2, I used a newly generate GUID. But I'm keep getting: XMLHttpRequest cannot load http://localhost:63918/api/values. The request was redirected to 'https://login.windows.net/devazureadnrw.onmicrosoft.com/wsfed?wa=wsignin1.0…3d0%26id%3dpassive%26ru%3d%252fapi%252fvalues&wct=2015-01-09T11%3a37%3a19Z', which is disallowed for cross-origin requests that require preflight.
Follwing is the Chrome console out:
renewToken is called for resource:http://localhost:63918/
adal.js:959 Add adal frame to document:adalRenewFrame
adal.js:959 Renew token Expected state: 9964340c-3c3b-4a2a-b710-f0d44f58655a|http://localhost:63918/
adal.js:755 Navigate url:https://login.windows.net/devazureadnrw.onmicrosoft.com/oauth2
authorize?re…e=9964340c-3c3b-4a2a-b710-f0d44f58655a%7Chttp%3A%2F%2Flocalhost%3A63918%2F
adal.js:959 Navigate to:https://login.windows.net/devazureadnrw.onmicrosoft.com/oauth2
authorize?re….onmicrosoft.com&domain_hint=devazureadnrw.onmicrosoft.com&nonce=undefined
adal.js:959 Add adal frame to document:adalRenewFrame
adal.js:959 State: 9964340c-3c3b-4a2a-b710-f0d44f58655a|http://localhost:63918/
adal.js:959 State status:true
adal.js:959 State is right
adal.js:959 Fragment has access token
adal.js:959 State: 9964340c-3c3b-4a2a-b710-f0d44f58655a|http://localhost:63918/
adal.js:959 State status:true
adal.js:959 State is right
adal.js:959 Fragment has access token
adal.js:959 Add adal frame to document:undefined
(index):1 XMLHttpRequest cannot load http://localhost:63918/api/values. The request was redirected to https://login.windows.net/devazureadnrw.onmicrosoft.com/wsfed?wa=wsignin1.0…3d0%26id%3dpassive%26ru%3d%252fapi%252fvalues&wct=2015-01-09T11%3a37%3a19Z', which is disallowed for cross-origin requests that require preflight.
adal.js:959 Add adal frame to document:undefined
adal.js:959 State: 9964340c-3c3b-4a2a-b710-f0d44f58655a|http://localhost:63918/
adal.js:959 State status:true
adal.js:959 State is right
adal.js:959 Fragment has access token
adal.js:959 State: 9964340c-3c3b-4a2a-b710-f0d44f58655a|http://localhost:63918/
adal.js:959 State status:true
adal.js:959 State is right
adal.js:959 Fragment has access token
The CORS pre flight request was made with 200 status code:
> values/api OPTIONS 200 OK text/plain angular.js:8560 Script 461 B 0 B
> 5 ms 4 ms
> authorize?response_type=token&client_id=1208eac1-f4dd-42f5-be33-886075f81be2&resource=http%3A%2F%2Flocalhost%3A63918%2F&redirect_uri=http%3A%2F%2Flocalhost%3A44302%2F&state=9964340c-3c3b-4a2a-b710-f0d44f58655a%7Chttp%3A%2F%2Flocalhost%3A63918%2F&prompt=none&login_hint=binjie%40devazureadnrw.onmicrosoft.com&domain_hint=devazureadnrw.onmicrosoft.com&nonce=undefined
> login.windows.net/devazureadnrw.onmicrosoft.com/oauth2 GET 302 Found
> text/html adal.js:297 Script
> 3.6 KB 0 B
> 1.30 s
> 1.29 s values/api GET 302 Found application/json Other 677 B 0 B 13 ms 13 ms
I'm stuck since this is the only sample I found. Any suggestions please? Many thanks.

This is Mat Velloso, I created that sample so you can shoot me :)
Here's what's going on: You are doing the CORS call, but the server is refusing it and asking you to authenticate. This can be for either of these three reasons (unless I'm missing something):
1-Either the Web API hasn't been properly configured to allow CORS (check my sample and the notes, I had exactly the same error at first because I didn't configure my Web API for it
2-Either you don't have a valid access token (which could be because the apps in AAD haven't been configured so one is allowed to call another, or just because you don't really have a valid access token)
3-Or either ADAL is not fetching the right access token for you because you didn't configure the endpoints collection (check how I initialize that in my app JavaScript, clearing out the right URLs)
Let me know if this helps, feel free to ping me for more info.
Regards,
Mat

sorry for the delay. I think I'mt not getting notifications so I missed it. Answering your questions:
1-The Guid is generated when you go to Azure Active Directory and create an app. It then assigns a new Guid as the client ID which identifies that's your app. Don't create a new Guid yourself, use the one identified as Client ID there.
2-Whether the app is deployed on Azure or running locally (or running anywhere else) is irrelevant. As long as you configure the reply URI correctly so the redirection falls where it is, you're good to go.
3-Once your user authenticates and you get a token back, whatever you do with that token is already on behalf of that user. So for example you can query the Azure Graph API and check in which groups that user is. The graph API is a REST api that lets you talk to Azure Active Directory. In order for that to work, keep in mind you need to give the app permissions to call Azure Graph API and read this sort of settings. Also remember that for every endpoint you call you need a specific access token (the one you get out of the authentication is only good for going back to AAD and asking for specific access tokens for specific things you want to access).
Perhaps a good way for you to start is taking a look at this: http://azure.microsoft.com/en-us/documentation/articles/mobile-services-how-to-register-active-directory-authentication/

Related

Is it possible to access microsoft graph using custom audience?

I have an API that uses Azure AD to provide access to resources. It has one scope api://{client_id}/Api.Read and following API permissions:
User.Read.All,
User.Read.
When I receive an access token from the AD it has only one aud - {client_id} and when I try to access Microsoft graph (e.g. https://graph.microsoft.com/v1.0/me) I get "Access token validation failure. Invalid audience." response as expected.
My question is - Is it possible to access both graph API and my API using one token and how do it?
I am exchanging current token to a "proper" token using on-behalf-of call: https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow
Basically, you just make the following call (copied from the link above):
POST /oauth2/v2.0/token HTTP/1.1
Host: login.microsoftonline.com/<YOUR_TENANT>
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&requested_token_use=on_behalf_of
&client_id=<YOUR_CLIENT_ID>
&client_secret=<YOUR_CLIENT_SECRET>
&assertion=<YOUR_CURRENT_TOKEN>
&scope=<YOUR_NEW_SCOPES>
The response contains the new token to access the resources that are requested in the scopes. My scenario is not exactly yours, but I think this should work for your case as well. You need to make this call server-side.
This assumes that the user has already consented that your app is okay to use <YOUR_NEW_SCOPES>, or otherwise this call will result in "interaction_required" or "invalid_grant" error (something like this, don't remember the exact error name) that basically means you need to raise UI prompt for the user to agree to the new scopes (how to do that is another question)

Setting additionalLoginParams with auth v2

I've followed this guide to configure access to my backend app, but I use Node backend and React frontend. However I'm unable to perform the step Configure App Service to return a usable access token. I get the error "Cannot execute the request for site x because the site is running on auth version v2.". To handle this I tried instead editing the sheet authsettingsV2, and I believe I found that the property properties.identityProviders.azureActiveDirectory.login.loginParameters in v2 equals properties.additionalLoginParams in v1 as editing this v2 property according to the tutorial shows the desired property in the v1 authsettings sheet.
However accessing my frontend app with this setting I get the error AADSTS901002: The 'resource' request parameter is not supported. before even being able to enter my credentials.
I've also tried without the additionalLoginParams setting completely, this gives me the error {"code":401,"message":"IDX10511: Signature validation failed. Keys tried: '[PII is hidden]'. \nkid: '[PII is hidden]'. \nExceptions caught:\n '[PII is hidden]'.\ntoken: '[PII is hidden]'."} on the API call, and upon inspecting my token, I find that the audience is the Microsoft Graph API. But I guess that is expected with this setup.
How can I proceed to enable access to my backend app? / How do I set the additionalLoginParams in auth version v2?
I am not sure if you have a correct access token. Please note that I am not talking about id token. Because from your response_type=code id_token, there should be only one id_token returned, but obviously the id_token cannot call your api, because what you need is an access token.
If you have not obtained a correct access token, then you should request an access token in the next step, refer to this link.
In addition, I must explain that all 401 errors are caused by api audiences. Therefore, make sure that your Application ID URI is set in the scope parameter.
When requesting an access token, you need to set the scope to: scope=openid api://{back-end api client id}/.default. Set response_type to: response_type=token.

Blazor WASM B2C - Redirect on AccessTokenNotAvailableException

I am working on a Blazor WASM application that uses B2C to authenticate the users and am having trouble getting the access token to refresh when I set the login mode to 'Redirect' rather than 'Popup'. I've pulled back my code to the absolute minimum which is essentially the tutorial at: https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-azure-active-directory-b2c?view=aspnetcore-5.0
As per Microsoft's examples, I wrap my http calls in a try/catch block as below with the intention that if the Access token expires, a new access token is retrieved and the page reloaded:
try
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
}
catch (AccessTokenNotAvailableException exception)
{
exception.Redirect();
}
For purpose of testing, I have set my Access Token timeout to 10 minutes and note that when this expires (and I had used the default Login mode of 'Popup'), a new Access token is obtained and the current page is returned to.
If however I change the LoginMode to 'Redirect' rather than 'Popup' and repeat the test, I receive the following error message within the address bar:
...authentication/login-failed?message=State%20mismatch%20error.%20Please%20check%20your%20network.%20Continued%20requests%20may%20cause%20cache%20overflow.
Interestingly though, it does appear to have updated the Access token as a quick flick between pages and I am able to retrieve the data.
Has anyone else experienced this or have any thoughts how to resolve this?

SharePoint Online multi-tenant REST calls return 404 on resources that definitely exist

I am attempting to access the SharePoint Online REST API (this is hand coded REST calls, no library being used).
Access tokens are acquired using authorization grant flow as follows:
I send the browser https://login.microsoftonline.com/common/oauth2/authorize?...
This redirects to a handler endpoint that we extract the access code from
I obtain the tenant ID by:
GET https://{tenantname}.sharepoint.com/_vti_bin/client.svc
Then extracting the tenant ID from the WWW-Authenticate header
I then POST https://login.microsoftonline.com/{tenantid}/oauth2/authorize to obtain the access token
When I use that access token, I am able to do queries using:
GET https://{tenantname}.sharepoint.com/_api/search/query?querytext=....
This works and returns documents.
But when I attempt to retrieve information about one of those documents:
GET https://{tenantname}.sharepoint.com/_api/web/getfilebyserverrelativeurl('/TestFiles/test.pdf')
I get a 404 response with the following body:
{"odata.error":{"code":"-2130575338, Microsoft.SharePoint.SPException","message":{"lang":"en-US","value":"The file /TestFiles/test.pdf does not exist."}}}
If I navigate to the URL in a browser (https://{tenantname}.sharepoint.com/TestFiles/test.pdf), it accesses the file without issue.
This makes me think that I'm running into some sort of permission issue.
I have tried setting the following scopes in the authorize redirect:
Attempt 1: scope = Web.Write AllSites.Write Site.Write
Attempt 2: scope = https://{tenantname}.sharepoint.com/.default
Attempt 3: scope = https://{tenantname}.sharepoint.com/Web.Write https://{tenantname}.sharepoint.com/AllSites.Write https://{tenantname}.sharepoint.com/Site.Write
No matter what I set as the scope parameter of the authorize URL, the JWT details of the access token show (I can post the entire decoded JWT if anyone needs it):
"scp": "User.Read"
Nothing I do has any impact on the scp in the token - I have no idea if that's the issue or not. If it is, I would appreciate hearing how to properly request scope.
The application registration in Azure Active Directory has desired permissions (plus more):
What am I doing wrong?
UPDATE: Switching to OAuth endpoint v2.0:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
With query parameters:
response_type = code
client_id = my app id
redirect_uri = my redirect uri
scope = <varying - I'll explain what happens under different scenarios below>
Here's what I've tried for scopes:
AllSites.Write Site.Write - the redirect has invalid_client with error_description = AADSTS650053: The application '' asked for scope 'AllSites.Write' that doesn't exist on the resource '00000003-0000-0000-c000-000000000000'. Contact the app vendor.
https://{tenantname}.sharepoint.com/AllSites.Write https://.sharepoint.com/Site.Write - the redirect has invalid_client with error description = AADSTS650053: The application '' asked for scope 'Site.Write' that doesn't exist on the resource '00000003-0000-0ff1-ce00-000000000000'. Contact the app vendor.
https://{tenantname}.sharepoint.com/.default - this goes through
But the resulting JWT has only scp=User.Read
The following works: GET https://{tenantname}.sharepoint.com/_api/search/query?querytext=
But the following returns a 404: GET https://{tenantname}.sharepoint.com/_api/web/getfilebyserverrelativeurl('/TestFiles/test.pdf')
I don't understand how Scope=.Default isn't including the allowed permissions from the application registration. And I definitely don't understand why the AllSites.Write scope is failing when it's explicitly specified.
If it helps, I have also tried all of the above using a tenant specific authorize endpoint instead of 'common':
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize
UPDATE2: More scope changes:
I finally found a magical combination that works:
Use a tenant based URI for the /authorize and /token endpoint and use {tenanturl}\AllSites.Write for the scope (do NOT specify the Site.Write scope):
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize?response_type=code&client_id={clientid}&redirect_uri={redirecturi}&scope=https%3A%2F%2F{tenantname}.sharepoint.com%2FAllSites.Write
The resulting JWT has the following:
"scp": "AllSites.Write User.Read"
I am completely perplexed about why Site.Write wasn't allowed. I suppose that AllSites.Write is a superset of Site.Write, so maybe not needed?
All of my testing so far has been on my own tenant, next step is to test on a different tenant and make sure it actually works there as well.
It seems you use v1.0 endpoint https://login.microsoftonline.com/common/oauth2/authorize but not v2.0 endpoint https://login.microsoftonline.com/common/oauth2/v2.0/authorize. If we use v1.0 endpoint, we should use resource instead of scope. So that is why the scp claim in your access token always the same no matter you modify the scope.
You should use resource with https://{tenant-name}.sharepoint.com and the parameter scope is useless when you use v1.0 endpoint.
If you still want to use scope parameter, you can also change the endpoint to v2.0. Just add v2.0 into your endpoint, like: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
I finally found a magical combination that works:
use the https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize and https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/token endpoints
specify {tenanturl}\AllSites.Write for the scope (do NOT specify the Site.Write scope - that was the primary problem):
https://login.microsoftonline.com/{tenantid}/oauth2/v2.0/authorize?response_type=code&client_id={clientid}&redirect_uri={redirecturi}&scope=https%3A%2F%2F{tenantname}.sharepoint.com%2FAllSites.Write
The resulting JWT has the following: "scp": "AllSites.Write User.Read"
This works across tenants and gets us the access we need.
For thoroughness, we also specify offline_access scope so we get a refresh_token in addition to the access_token.

High Trust S2S Provider Hosted App with "App + User" Policy

I am relatively new to sharepoint app development.
Trying to create a on premises, High Trust provider hosted app with App + User Policy. I have followed below document to create a demo.
https://msdn.microsoft.com/library/office/fp179901(v=office.15)
http://blogs.msdn.com/b/russmax/archive/2014/06/23/part-1-intro-to-provider-hosted-apps-setup-the-infrastructure.aspx
I am facing few issue and I have some question to clarify, if anybody can help.
1) When I inspect my request in dev tools, it give me below form data.
SPAppToken:
SPSiteUrl:
SPSiteTitle:Home
SPSiteLogoUrl:
SPSiteLanguage:en-US
SPSiteCulture:en-US
SPRedirectMessage:EndpointAuthorityMatches
SPErrorCorrelationId:f069e89c-a0cd-20ce-a1c0-7db95db0334b
now when i inspect log with above corelation id, i am finding below errors.
-- Error when get token for app i:0i.t|ms.sp.ext|ab8ff461-bc75-4516-b475-b666ac47eec0#802f23e1-6e11-45d1-909c-07a7b0ab0ce2,
exception: Microsoft.SharePoint.SPException: The Azure Access Control
service is unavailable.
-- App token requested from appredirect.aspx for site: 92bfe5c4-7255-4b09-a89a-07e0e2b03622 but there was an error in
generating it. This may be a case when we do not need a token or when
the app principal was not properly set up.
-- Getting Error Message for Exception Microsoft.SharePoint.SPException: The Azure Access Control service is
unavailable.
a) I belive in high-trust app it shouldn't look for Azure ACS.
Is this error because of some incorrect configuration?
b) SPAppToken is null here. Is it null always in case of hig trust app?
2) Say I am logged into sharepoint with User A and trying to launch sharepoint app.
Within app code I want to get identity of logged in user(which is A). From below code i found that Request.LogonUserIdentity gives me identity of user A. But how can we sure that request is came from sharepoint only. I can copy the same app URL and paste in browser window and login with window credential and get the same result. So question is how can I verify if its legitimate request came from sharepoint only and no one is faking request.
ALos, when I inspect request in dev tools, its passing Authorization key in request header. What is use of this?
using (var clientContext = TokenHelper.GetS2SClientContextWithWindowsIdentity(hostWeb, Request.LogonUserIdentity)) { clientContext.Load(clientContext.Web, web => web.Title); clientContext.ExecuteQuery(); Response.Write(clientContext.Web.Title); }
3) Also what happens if my app doesnt support windows authentication and only support FBA, is there any way to get user identity in this case?
Any help would be much appreciated.
Thanks
For issue #1: It looks to me that the step # 9 (Configure authentication settings) in this section (from the first MSDN article you have referred) was missed, i.e., 'ACS Control service' was selected instead of 'Use a Certificate' option.
For issue #2: There are helper methods in TokenHelper.cs to validate the AccessToken from the HttpRequest, which identifies the validity of the request.

Resources