I have a native client application which is obtaining an OAuth2 token for Active Directory authorization. It will then use that token to communicate with a secure Web API server where certain areas of the API are secured using the [Authorize] attribute. The server is also registered with Azure AD and can properly authorize requests through AD.
When I try to get the token, I get the following exception on Line 2 of the code below:
Additional information: invalid_request: AADSTS90027: The client '<Client GUID>' and resource 'https://abccompany.com/MyApplication.Server' identify the same application.
Here is my code which I am running in the native client (just on a button press as a test, for now). Obviously the GUID and company names have been obfuscated.
AuthenticationContext ac = new AuthenticationContext("https://login.windows.net/abccompany.com");
AuthenticationResult ar = ac.AcquireToken("https://abccompany.com/MyApplication.Server", "<Client GUID>", new Uri("https://localhost:44300/secure"), PromptBehavior.Auto);
I made sure the redirect existed in Azure in the application configuration (otherwise there would have been a redirect error instead). What does the error mean?
You seem to be using the clientId of the WebAPI where you need to supply the clientId of the client app. Please register a separate 'Native client application' in Azure AD representing the client app.
The following topic explains the protocol flow and how to register WebAPIs in Azure AD such that users from multiple AD tenants can use that API: http://msdn.microsoft.com/en-us/library/azure/dn499820.aspx#BKMK_Native
The following samples should see you through:
Single tenant WebAPI: https://github.com/AzureADSamples/NativeClient-WindowsStore
Multi-tenant WebAPI: https://github.com/AzureADSamples/NativeClient-WebAPI-MultiTenant-WindowsStore
Hope this helps.
ps: Azure AD doesn't issue a token when the client and resource are the same application. In your case they should indeed be different and resource clientid (issued to a confidential client) should not be used as a public client - however for service to service scenarios, it can be argued that issuing tokens to self should be allowed - this is something we are looking into.
Related
I have a standalone Blazor WASM site (client), a separate .NET 6 web API (server) with protected endpoints and I'm trying to call MS Graph from the API.
I've read just about every article I could find on the configuration required to make this work and I'm stuck with the incremental consent failing. I get the following error when trying to access a server API which uses MS Graph:
Error acquiring a token for a downstream web API - MsalUiRequiredException message is: AADSTS65001: The user or administrator has not consented to use the application with ID '[redacted]' named '[redacted]'. Send an interactive authorization request for this user and resource.
Configuration...
Created AAD app for Web API (server), added secret for Graph configuration, set the app URI and created access_as_user scope under "Expose an API" in AAD.
Added the client ID (from the following step) to the knownClientApplications section in the manifest for the server app registration in AAD.
For API Permissions I added Graph scopes User.Read, User.Read.All, and Group.Read.All and provided admin consent in the AAD UI.
Configured appsettings.json in the API to add the Graph API BaseUrl and above scopes from step 2 along with the correct AzureAD domain, TenantId, ClientId, and ClientSecret values for MSAL to function.
Configured MSAL on the server:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
Created AAD app for Blazor WASM, used SPA auth w/redirect to https://localhost:7014/authentication/login-callback and set the API permissions to api://[redacted]/access_as_user only.
Created custom authorization message handler according to this article.
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[]
{
"https://localhost:7069"
},
scopes: new[]
{
"api://[redacted]/.default"
});
}
Configured MSAL on the client:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://[redacted]/.default");
options.ProviderOptions.LoginMode = "redirect";
}
Set up named HTTP client on the Blazor client with custom message handler:
var baseAddress = builder.Configuration["PublicApiUrl"];
builder.Services.AddHttpClient("PublicApi", client =>
{
client.BaseAddress = new Uri(baseAddress);
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PublicApi"));
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
What works...
I can authenticate as an AAD user to the Blazor client.
I can access protected endpoints (using policy-based authorization) hosted on the server which don't have a dependency on MS Graph.
Questions...
Following this article's guidance about incremental consent, specifically the "Static permissions" section, I would assume granting admin consent for Graph on the server's app registration would suffice?
All of the documentation showing Blazor WASM with a protected API calling a protected API (Graph) assume the Blazor client is also hosted by the API server. Is it even possible to use on-behalf-of flow in my case? If it was hosted I could see the API calling the Blazor navigation subsystem to perform an incremental consent redirect but when they're separated, I can only imagine the static permissions is the way to go.
Is it necessary to set the DefaultAccessTokenScopes in the client?
The issue here is use of the AddMicrosoftGraph method when the API application is being built.
The GraphServiceClient created by AddMicrosoftGraph will have default access to delegated permissions which are assigned to users as opposed to application permissions which are assigned to applications. This is why the MsalUiRequiredException is being thrown which is usually resolved by prompting the user to login.
You can read more about delegated vs application permissions here.
What you can do instead is use the AddMicrosoftGraphAppOnly method to create a GraphServiceClient that will use credentials specific to your API to retrieve the relevant data needed from the Microsoft Graph API.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraphAppOnly(
authenticationProvider => new GraphServiceClient(authenticationProvider))
.AddInMemoryTokenCaches();
So long as you have the relevant settings and secrets provided in the AzureAd section of your appsettings.json file the GraphServiceClient injected into your application should now be able to access the data you need.
You can read more about app configuration with the AzureAd settings in your appsettings.json file here.
I have a web application that allows me to sign in to my OneDrive account using delegated permissions to authorize the app to browse my drive files on my behalf. I'm now trying to build a server-side job that needs to work with these files and therefore needs application permissions granted with admin consent.
I've followed various instructions to achieve this, but no matter what I do I keep getting this 403 error:
Either scp or roles claim need to be present in the token
The application I've registered in Azure portal has the application permission Files.Read.All and I've granted admin consent. I'm obtaining my access token as follows:
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("<client-id>")
.WithClientSecret("<secret>")
.WithAuthority(new Uri("https://login.microsoftonline.com/common"))
var apiUrl = "https://graph.microsoft.com/";
string[] scopes = { $"{apiUrl}.default" };
result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
I'm then using the returned token to request "{apiUrl}v1.0/drives/<my-drive-id>/items/<drive-item-id>"
And this is where I get the access denied. Clearly the API expects my token to have either a roles claim or an scp claim.
After reading this related post on SO I did wonder if the problem is related to the fact I'm using the same app registration for the front end and back end operations (so it has a mix of delegated and application permissions) but I tried creating a new app registration with only the application permissions and it was the same error.
Also, that post suggests I should expect to see either and scp or a roles claim in my token (depending on choice of auth flow) but I get neither of these claims. That led me to this other SO post which suggests I need to explicitly include roles in my access token, but roles isn't listed as an optional claim in the Token configuration blade of the app registration.
So I'm stuck. Can anyone help?
UPDATE 1
I've tried constructing the auth request manually now using a POST to https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token and a form body that includes grant_type=client_credentials and now I'm finding the roles claim is returned in the token. So I guess the ConfidentialClientApplicationBuilder wasn't building a client credentials auth request as I had assumed. However, when I use this token to make the above Graph API request for a drive item I get the error:
Tenant does not have a SPO license
When I've encountered this before I've been told to use common instead of my tenant ID. However, when I make that change in this case I'm again left with a token that doesn't have a roles claim.
The absence of the roles claim indicates that you app (or service) hasn't been granted any application permissions (i.e. app roles) for the API (in this case, Microsoft Graph), in the tenant where the token request is being made.
You need to ensure the the application permissions (app roles) you expect the app to use to make the API call have been granted in the tenant where you are making the API call.
I am struggling to get a token from "https://login.microsoftonline.com/common/oauth2/token" with an Azure function by a post-request. The token will give permissions to access SharePoint though CSOM. Here is my code snippet with the post request:
var clientId = defaultAADAppId;
var body = $"resource={resource}&client_id={clientId}&grant_type=password&username={HttpUtility.UrlEncode(username)}&password={HttpUtility.UrlEncode(password)}";
using (var stringContent = new StringContent(body, Encoding.UTF8, "application/x-www-form-urlencoded"))
{
var result = await httpClient.PostAsync(tokenEndpoint, stringContent);
var tokenResult = JsonSerializer.Deserialize<JsonElement>(result);
var token = tokenResult.GetProperty("access_token").GetString();
}
When testing locally, both when running the function in Visual studio and when I try with Postman, I am able to achieve an access token. However, as soon as I publish the function to my Function app in Azure I receive the following error message:
"AADSTS53003: Access has been blocked by Conditional Access policies. The access policy does not allow token issuance"
I have enabled an app registration in the portal and as mentioned, it all works fine until I publish everything to Azure.
Any ideas on how to solve this?
I got it to work now. First of all I reviewed the CA policies as #CaseyCrookston suggested. What I found out was that our CA policies blocked calls outside the country we operate from. However, the calls from the App registration/Azure function were registered from the Azure data centre location and thus, blocked by our CA policies. When running them locally the calls where registered in my country and therefore no errors were showing while debugging.
My first step was trying to add my Client app to the CA policy, which was not possible. The client/secret authentication that I used based on the suggestions in this CSOM guide by Microsoft prevented the App registration to be whitelisted from the CA policies (Github issue).
Based on this I had to change the authentication to a Certificate-based authentication as suggested here: Access token request with a certificate and here: SO answer. With this I was able to whitelist the App registration in the CA policies and successfully authenticate to the Sharepoint CSOM.
As the error message says, your app is blocked by CA policy. Possible causes can be unknown client app, blocking external IP addresses, etc.
You can perform one of the below workarounds:
Add your Client app to your CA policy.
I wouldn’t recommend this because this affects your security - if you take the risk you could exclude the “Microsoft Azure Management” from your CA policy which blocks unknown clients / requires device state and still protect the sign-in with MFA.
A better approach is to use another OAuth 2.0 and OpenID connect flow like the delegated flow where you sign-in directly within the app, if possible.
According to this official Microsoft document (and the documents referenced there) URIs using login.microsoftonline.com/myTenant/.. must be replaced by myTenant.b2clogin.com/mytenantId/..
For the interactive login with MSAL to get an ID token this works fine for me. But I can not figure out which URI to use with IConfidentialClientApplication and using a secret. I'm using MSAL 4.18 in a C# Windows application.
The following code works fine using login.microsoftonline.com.
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create(clientId)
// how must this URL look like when using b2clogin.com
.WithAuthority("https://login.microsoftonline.com/*MyTenant*.onmicrosoft.com/v2.0")
.WithClientSecret(secret)
.Build();
AuthenticationResult authenticationResult = await app
.AcquireTokenForClient(scopeArray)
.ExecuteAsync();
Whatever b2clogin.com URI I use (e.g. https://*myTenant*.b2clogin.com/*myTenantId*/v2.0) I get back an AADSTS50049: Unknown or invalid instance. response.
What am I missing? Can I continue to use login.microsoftonline.com for this case?
(This answer comes with a bit of twists, so please read till end :))
You should not generally continue to use login.microsoftonline.com for B2C, it will be retired on December 4, 2020.
Authority should be in format https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-ID}/{policyname} or https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{policyname}
Also, you should be using .WithB2CAuthority, not .WithAuthority for B2C in code for client setup (but there is a catch here for your case since it's Client Credential Grant which is in the later part of the answer).
Example code (though the example uses public client, in your case it's confidential client, just referring it for authority reference).
For details, refer this.
But since you are using Client Credential Grant Flow using client id and secret, it's not directly supported in B2C as documented here.
Although the OAuth 2.0 client credentials grant flow is not currently
directly supported by the Azure AD B2C authentication service, you can
set up client credential flow using Azure AD and the Microsoft
identity platform /token endpoint for an application in your Azure AD
B2C tenant. An Azure AD B2C tenant shares some functionality with
Azure AD enterprise tenants.
In summary, you can not use B2C directly for Client Credential Grant and workaround requires to use regular common enterprise AAD backend of B2C. So b2clogin.com part would not be applicable for this workaround. That mean only for this particular Client Credential Flow case, you should be continuing with login.microsoftonline.com since you are not actually using B2C in true sense for this.
I have deployed an API App to Azure, but I am having problems creating API Client if Authentication (with AAD) is set to ON.
When I try to generate service client (when Authentication is OFF), then client code is generated (it's done with Autorest) and code is working, but when I switch Authentication ON (and Action to take when request is not authenticated is set to Login with Azure Active Directory), then
1) service call returned 401 Unauthorized (without redirecting to AAD login page)
2) Then I tried to generate service client once more (from Project's context menu -> Add -> REST API Client -> then in the dialog box I chose "Select Azure Asset" and pressed OK and got a message "Failed to download metadata file for Microsoft Azure API App: ...app name..." (and "no additional information available")
I was implementing AAD according to this Azure manual (using express settings):
https://azure.microsoft.com/en-us/documentation/articles/app-service-mobile-how-to-configure-active-directory-authentication/
Was working according to this video, too and everything what is shown in this video was working, except that AAD was not demonstrated... and for me it's not working...
https://azure.microsoft.com/en-us/documentation/videos/connect-2015-what-s-new-in-app-service-api-apps/
Any suggestions?
EDIT
1) If I enter the request url (that REST API client uses) in web browser - then it returns valid results
2) I found out that I am using REST API without credentials (I thought Azure AD login screen should be presented in this case... but it isn't)
EDIT 2
I got some progress - got to the AAD login screen, but after entering credentials I get the bearer token, but when I try to query the service, I get an error message:
AADSTS65005: The client application has requested access to resource 'https....azurewebsites.net'. This request has failed because the client has not specified this resource in its requiredResourceAccess list.
Trace ID: 4176e...
Correlation ID: 1d612d...
Timestamp: 2016-11-13 18:28:34Z
These are the steps I've done to get this far:
0) Added Microsoft.IdentityModel.Clients.ActiveDirectory nuget pack to client project
1) registered my client app in Azure Active Directory
2) when calling REST API from client application, I am adding ServiceClientCredentials
3) when creating ServiceClientCredentials I provide 4 elements
-authority = this is from AAD App registrations -> Endpoints => Federation Metadata Document vērtība (without the starting part http://login.windows.net/)
-resource => this is REST API uri (=>Identifier of the target resource that is the recipient of the requested token)
-clientId => this is application id I get after I registered client app in AAD
-redirect Uri => since my client app is a Native application, then this is just any valid url
How can I specify this resource in my client app?
client has not specified this resource in its requiredResourceAccess list
I managed to find a solution on how to enable AAD authorization to Azure REST API App. Just in case anyone has the same challenge, I hope this will be helpful.
These are the steps I did:
1) In App services -> Authentication/authorization
App Service Authentication => On
Action to take when request is not authenticated => Login with AAD
Configured AAD with Express settings (there you have to create Azure
AD App for you API App - i.e. "App registration" for your service)
2) In Azure Active Directory -> App registrations
Add registration for your client app
Edit Manifest of your client app - in the requiredResourceAccess section you must add information about REST API App:
resourceAppId -> insert REST API App id here
resourceAccess {id} -> OauthPermission id value of REST API (you can get it in REST API's manifest!)
3) In your client application
generate your REST client using Autorest (from solution explorer: Add\REST API client) or create it manually
add Microsoft.IdentityModel.Clients.ActiveDirectory nuget pack
get and use token to access your API with code similar to this:
//request
(..)
var tokenCreds = getToken();
ServiceClientCredentials credentials = tokenCreds;
using (var client = new YourAPI(credentials)) {
...
}
(..)
//getting token
private static TokenCredentials getToken()
{
//get this from Federation Metadata Document in
//Azure Active Directory App registrations -> Endpoints
var authority = "f1...";
//Identifier of the target resource that is the recipient of the requested token
var resource = "https://yourapi.azurewebsites.net";
//client application id (see Azure Active Directory App registration
//for your client app
var clientId = "a71...";
//return url - not relevant for Native apps (just has to be valid url)
var redirectUri = "https://just-some-valid-url.net";
AuthenticationContext authContext =
new AuthenticationContext(string.Format
("https://login.windows.net/{0}",
authority));
AuthenticationResult tokenAuthResult =
authContext.AcquireTokenAsync(resource,
clientId,
new Uri(redirectUri),
new PlatformParameters(PromptBehavior.Auto)).Result;
return new TokenCredentials(tokenAuthResult.AccessToken);
}