In JWT from AAD there is a key 'aud'. https://jwt.io/, says it is 'Audience. (Who or what the the token is intended for)'. My question is, Are aud values website specific - can I check the aud and expect it to be same to check if the token is intended for my specific site?
In Azure AD, the audience value always indicates the resource the token is targeted on.
You can acquire an access token by using either the API's client id or Application ID URI.
What you use will be the audience in the token.
So if you make an API, you should check the audience is either the API's client id or Application ID URI.
You can know for sure it will always be one of those if the token is meant for your API.
EDIT: The below information is not correct.
If I know your API's identifier + your tenant id,
I can acquire an access token for your API using client credentials!
The token will not contain scopes or roles, it cannot.
So it is critical that you check for the presence of valid delegated permissions (aka scopes) or valid app permissions (in roles claim).
THIS IS WRONG: If I tried to acquire an access token using your API's identifier from my AAD tenant, it would not give me a token.
Any app that passes an access token with the correct audience had rights to call your API when it acquired the token.
You already got a good explanation of the audience value from juunas.
I'm adding here a specific code example from Azure-Samples on Github which shows how to validate the JWT Token manually and checks among other things audience value. (It's pretty important to validate issuer as well)
Look at this particular code and especially near the comment "We accept both the App Id URI and the AppId of this service application"
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// We accept both the App Id URI and the AppId of this service application
ValidAudiences = new[] { audience, clientId },
// Supports both the Azure AD V1 and V2 endpoint
ValidIssuers = new[] { issuer, $"{issuer}/v2.0" },
IssuerSigningKeys = signingKeys
};
Code Sample:
Specific file with code excerpt shown above
Azure-Samples: Manually validating a JWT access token in a web API
Related
I want, when user login he should get list of tenant, from that list, user decide in which tenant he want to redirect
I want to call https://graph.microsoft.com/v1.0/organization API but when I write code to call it , it will returned error, I have get token using below code, it is worked for users API of Graph, but not working for organization api
B2BGraphClient.AccessToken = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credential).ConfigureAwait(false);
I have checked it using Postman, when I have pass token generated using https://login.microsoftonline.com/common/oauth2/token this api, organization api returns correct output, but logically it is not possible in code to pass userid and password to api and get token, below is image of postman call
I want correct way to do this
Your code is using Client_credentials flow but in Postman you are using ROPC flow.
The two flows use different permission type. Client_credentials flow uses Application permission while ROPC flow uses Delegated permission.
So for Client_credentials flow, if the app belongs to a work or school (organization) context then for https://login.microsoftonline.com/common/oauth2/token replace common with a tenantId or domain name. If you don't do this, you will get the Authorization_IdentityNotFound error.
Specify the tenant in the code (modify the sample code based on your needs).
this.clientId = clientId;
this.clientSecret = clientSecret;
this.tenant = tenant;
// The AuthenticationContext is ADAL's primary class, in which you indicate the direcotry to use.
this.authContext = new AuthenticationContext("https://login.microsoftonline.com/" + tenant);
// The ClientCredential is where you pass in your client_id and client_secret, which are provided to Azure AD in order to receive an access_token using the app's identity.
this.credential = new ClientCredential(clientId, clientSecret);
B2BGraphClient.AccessToken = await this.authContext.AcquireTokenAsync("https://graph.microsoft.com/", this.credential).ConfigureAwait(false);
And don't forget to add one of the following Application permissions Organization.Read.All, Directory.Read.All, Organization.ReadWrite.All, Directory.ReadWrite.All in your app registration as per Permissions.
UPDATE:
In fact this endpoint https://graph.microsoft.com/v1.0/organization can't return the tenants which the user is member of.
You have found the correct Azure rest API to list the tenants.
But this API also doesn't support client_credentials flow.
In another word, you cannot use authContext.AcquireTokenAsync to get the token. You should consider AcquireTokenByAuthorizationCodeAsync. And specify the scope as https://management.azure.com/ instead of https://graph.microsoft.com.
Don't forget to add the Azure Rest permission in app registration.
I have a multi tenant Web app / API registered in Azure ad and that connected to an API App. The API App has Active Directory Authentication setup. At the moment only one other tenant needs access to api. I made sure only they can get access by putting https://sts.windows.net/<third party tenant>/ in the Issuer URL. My question is: How would I go about giving a second (or more) tenants access to the api? I can't add anymore tenant ids in the Issuer URL so I'm kinda at a loss
Thanks
The approach you are using currently will work only in a single tenant scenario, i.e. Automatic validation of tenant by setting IssuerURL works only in a single tenant scenario.
In case of multi-tenant applications, the application is responsible for storing and validating all possible issuers. This is by design and exact guidance on this topic from Microsoft is available here:
Work with claims-based identities in Azure AD: Issuer Validation
For a single-tenant application, you can just check that the issuer is
your own tenant. In fact, the OIDC middleware does this automatically
by default. In a multi-tenant app, you need to allow for multiple
issuers, corresponding to the different tenants. Here is a general
approach to use:
In the OIDC middleware options, set ValidateIssuer to false. This turns off the automatic check.
When a tenant signs up, store the tenant and the issuer in your user DB.
Whenever a user signs in, look up the issuer in the database.If the issuer isn't found, it means that tenant hasn't signed up. You
can redirect them to a sign up page.
You could also blacklist certain tenants; for example, for customers that didn't pay their subscription.
So, in case of a .NET based web application the code in your startup class would change to something like this.. notice the new TokenValidationParameters { ValidateIssuer = false }
Authenticate using Azure AD and OpenID Connect
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions {
ClientId = configOptions.AzureAd.ClientId,
ClientSecret = configOptions.AzureAd.ClientSecret, // for code flow
Authority = Constants.AuthEndpointPrefix,
ResponseType = OpenIdConnectResponseType.CodeIdToken,
PostLogoutRedirectUri = configOptions.AzureAd.PostLogoutRedirectUri,
SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme,
TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false },
Events = new SurveyAuthenticationEvents(configOptions.AzureAd, loggerFactory),
});
Once you have disabled the Validate issuer, you will need to handle the validation yourself. Here is a sample with some guidance around how to do this validation yourself
Update your code to handle multiple issuer values
You will at least need to check the "tid" claim which captures the Azure AD Tenant Id against your own list of valid tenant IDs, before you let the call go through.
When a single tenant application validates a token, it checks the
signature of the token against the signing keys from the metadata
document, and makes sure the issuer value in the token matches the one
that was found in the metadata document.
Since the /common endpoint doesn’t correspond to a tenant and isn’t an
issuer, when you examine the issuer value in the metadata for /common
it has a templated URL instead of an actual value:
https://sts.windows.net/{tenantid}/
Therefore, a multi-tenant application can’t validate tokens just by
matching the issuer value in the metadata with the issuer value in the
token. A multi-tenant application needs logic to decide which issuer
values are valid and which are not, based on the tenant ID portion of
the issuer value.
For example, if a multi-tenant application only allows sign in from
specific tenants who have signed up for their service, then it must
check either the issuer value or the tid claim value in the token to
make sure that tenant is in their list of subscribers. If a
multi-tenant application only deals with individuals and doesn’t make
any access decisions based on tenants, then it can ignore the issuer
value altogether.
(EDIT) More information on Validating Tokens
I'm trying to answer your questions from comments here.
Here is sample code which does the task of manually validating JWT tokens.
Manually validating a JWT access token in a web API
A useful excerpt..
Validating the claims When an application receives an access token
upon user sign-in, it should also perform a few checks against the
claims in the access token. These verifications include but are not
limited to:
audience claim, to verify that the ID token was intended to be given
to your application not before and "expiration time" claims, to verify
that the ID token has not expired issuer claim, to verify that the
token was issued to your app by the v2.0 endpoint nonce, as a token
replay attack mitigation You are advised to use standard library
methods like JwtSecurityTokenHandler.ValidateToken Method
(JwtSecurityToken) to do most of the aforementioned heavy lifting. You
can further extend the validation process by making decisions based on
claims received in the token. For example, multi-tenant applications
can extend the standard validation by inspecting value of the tid
claim (Tenant ID) against a set of pre-selected tenants to ensure they
only honor token from tenants of their choice.
Sample Access Token, just for understanding: Access Token and Id_token are both simple base64 encoded JSON Web Tokens (JWT). You can decode these to find the claims and then validate them. I'm sharing a sample which has code to do just that. Before that here is a sample access token from one of Microsoft Docs. I just took one for example from here
Actual Value: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1N... (a long encoded string continues)
Decoded Value (you can check this easily using a website like https://jwt.io):
{
"aud": "https://service.contoso.com/",
"iss": "https://sts.windows.net/7fe81447-da57-4385-becb-6de57f21477e/",
"iat": 1388440863,
"nbf": 1388440863,
"exp": 1388444763,
"ver": "1.0",
"tid": "7fe81447-da57-4385-becb-6de57f21477e",
"oid": "68389ae2-62fa-4b18-91fe-53dd109d74f5",
"upn": "frankm#contoso.com",
"unique_name": "frankm#contoso.com",
"sub": "deNqIj9IOE9PWJWbHsftXt2EabPVl0Cj8QAmefRLV98",
"family_name": "Miller",
"given_name": "Frank",
"appid": "2d4d11a2-f814-46a7-890a-274a72a7309e",
"appidacr": "0",
"scp": "user_impersonation",
"acr": "1"
}
As you can see the decoded value has many claims including "tid" which you're about to validate.
Hope this helps!
Need help in authenticating the token request from any client application to WEB API. We have registered our web API has multi-tenant application in Azure AAD. My client application which has been registered in different tenant was able to get Access token from AAD.while making Http request to our endpoint with passing the access token part of request header, we are receiving 401 unauthorized exception. I found reason while browsing for multi tenant scenario is to disable ValidateIssuer and have custom handler.
• Is there any custom handler on implementing for WindowsAzureActiveDirectoryBearerAuthentication. I see people are using OpenIDConnect. But for WEB API, we are using WindowsAzureActiveDirectoryBearerAuthentication i.e Is there any equivalent Event for validation of access token in UseWindowsAzureActiveDirectoryBearerAuthentication and tell user is authenticated ?.
• Is there any better standard of validation of access token and tell user is valid user ?.
• Can we get the claims of user by passing bearer token to WEBAPI Authorize filter ?. or will httprequest object claims gets user information like given name, tenant name, object ID (esp. localhost debugging scenario also.), If we can get those information, we can have our own logic of validation.
Please let us know whether this approach is best practice for authenticating a user.
You could implement a custom issuer validator and assign it to the IssuerValidator property. This is useful when you can't specify a predefined list of issuers in configuration and need some runtime logic to determine if you trust the issuer presented in the token:
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
IssuerValidator = (issuer, token, tvp) =>
{
if (db.Issuers.FirstOrDefault(b => (b.Issuer == issuer)) == null)
return issuer;
else
throw new SecurityTokenInvalidIssuerException("Invalid issuer");
}
}
You could decode the access token to get basic user information like family_name/given_name , but you can only get that by using user identity to acquire the access token .
I have create a webapi secured with azure active directory. I need to test this now and trying to use fiddler with an authorization header. I am trying to generate the token with below code.
Target obj = (Target)cmbTarget.SelectedItem;
AuthenticationResult authenticationResult;
string aadInstance = obj.AADInstance; // "https://login.windows.net/{0}";
string tenant = obj.Tenant; //"rudderless.onmicrosoft.com";
string apiResourceId = obj.ApiResourceId; //"15b4ac7f-23a8-4958-96a5-64159254690d";
string clientId = obj.ClientId; // "47cdc6c3-226a-4c38-b08e-055be8409056";
Uri redirectUri = new Uri(obj.RedirectUri); //new Uri("http://nativeclient");
string authority = string.Format(aadInstance, tenant);
authContext = new AuthenticationContext(authority);
authenticationResult = this.authContext.AcquireToken(apiResourceId,
clientId, redirectUri, PromptBehavior.Always);
txtToken.Text = authenticationResult.AccessToken;
Clipboard.SetText($"Bearer {txtToken.Text}");
I get the token generated successfully and when I am using the token to call the webapi it throwing 401 with message
WWW-Authenticate: Bearer error="invalid_token", error_description="The
audience is invalid"
I think it is important to revisit the different steps of authentication, and hopefully through the discussion you will be able to solve the issue you are having.
When a client is trying to get an access token to a resource, it needs to specify to AAD which resource it wants to get a token for. A client may be configured to call multiple resources, all with different configurations, so it is an expectation that the resource is always specified in an Access Token Request.
The resource can either be an App ID GUID for the Resource, or a valid App ID URI which is registered on the Resource. AAD should be able to uniquely identify which resource you are trying to reach based on the value you provide. However, note that if you use an App ID GUID, you will get a token from AAD where the Audience claim is the App ID GUID. Alternatively, if you use an App ID URI, you will see that URI as the audience claim in the token.
In both situations, you will get a token for the 'same' resource, but the claim in the token will appear differently. Additionally, it may be possible that a single application resource may have multiple App ID URIs registered on their app. Depending on which one you use in the authentication request, you will get a different audience claim in the token which matches the resource parameter you passed in.
Finally, once you get the token, you send it over to the Resource API who will validate the token for a number of things, such as: the Client ID Claim, the Scopes/Roles Claims, the authentication method ('acr' claim), and definitely that the audience claim matches what they expect!
This means that the Resource API ultimately needs to say "I accept < App ID GUID > as a valid Audience Claim"... or "I accept < App ID URI > as a valid Audience Claim". This kind of logic may be built into the library you are using (like OWIN), but you need to make sure that on your API side, you have it configured correctly for the Audiences you expect. You could, if you wanted, make it so that your API does not check the Audience claim at all! All the claims in the token are plaintext, and thus you could really do whatever you want, but you would not have a very secure API in that situation :]
End of the day, my hunch is that this error is coming from your own API, and it is happening because you have not configured your app to accept an Audience claim which matches your Resource's App ID GUID (which it looks like what you are passing when you are getting a token based on your code sample).
I hope this solves your issue!
Problem
After implementing the instructions found in this Protected web API: Code configuration article, I received an error message similar to the OP's:
WWW-Authenticate: Bearer error="invalid_token", error_description="The
audience is invalid"
The problem turned out to be my AzureAd > ClientId setting in my appsettings.json file.
Solution
I updated the appsettings.json file of my ASP.NET Core Web API app so that the ClientId setting used the "Application ID URI" found in portal.Azure.com under my App Registriation > "Expose An API" section.
The section in appsettings.json looks similar to this:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "XXXXXXXX-XXXXX-XXXXX-XXXXX-XXXXXXXXXX",
// ClientId = Portal.Azure.com > App Registration > Expose an API > "Application ID URI"
"ClientId": "api://XXXXX-XXXXXX-XXXXX-XXXX-XXXXXXXXX"
}
Important note
"aud" value that is being generated for JWT token by azure is also controlled by "accessTokenAcceptedVersion" property in AD application manifest.
This property defines a version of the access token that will be generated (MS docs about accessTokenAcceptedVersion).
Possible results for its values:
null or 1 - "api://" prepended to GUID
2 - "api://" is not added, so there should be GUID only
I had the same issue. Thought of sharing it. I have change the Web Api Audience to the ClientId of the Web App. After this it works.
The Microsoft references show the following example:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "[Client_id-of-web-api-eg-2ec40e65-ba09-4853-bcde-bcb60029e596]",
"TenantId": "common",
"Audience": "custom App ID URI for your web API"
},
// more lines
}
Can also be that your app/lib is using a newer version of the api.
If accessTokenAcceptedVersion is null in the manifest of your app ms defaults to v1.
Check your jwt token in http://jwt.io
If you get this - check your JWT Token. If ISS isn't like this
"iss": "https://login.microsoftonline.com/[yadyada]/v2.0",
then most likely you're using another version (like version 1 which is default). Check the manifest of your azure ad app:
Below value is probably null or one, should be two:
"accessTokenAcceptedVersion": 2,
I had the same issue. I was using the client's Resource ID as the parameter for AcquireToken when I should have used the server's Resource ID.
It works when I use the correct Resource ID.
I got the same error. It was because I was using a custom domain, so my API ID URL wasn't api://{client-id}.
The solution is to set the Audience setting on your appsettings.json, just like mentioned in the Microsoft Wiki:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "XXXXXXXX-XXXXX-XXXXX-XXXXX-XXXXXXXXXX",
"ClientId" : "XXXXXXXX-XXXXX-XXXXX-XXXXX-XXXXXXXXXX",
// Audience = Portal.Azure.com > App Registration > Expose an API > "Application ID URI"
"Audience": "Application ID URI"
}
While calling api for implementing service principle through App registration in active directory.
I got this error while calling api-GET {vaultBaseUrl}/secrets/{secret-name}/{secret-version}?api-version=7.0 with bearer key to get key vault secret value.
As part of fix, to get bearer value, Apart from passing clientid, client secret, grant_type,I added resource key with value https://vault.azure.net as part of request body of api call for https://login.microsoftonline.com/{ActiveDirectoryId}/oauth2/token.
This might help someone: I've encountered this error because the MS Graph User.Read permission was missing on the SharePoint Online Client Extensibility Web Application Principal. Out of the box, this app reg already has the User.Read permission, but I had removed that one because (for an earlier project) I already used User.Read.All, thinking that it included User.Read. However, User.Read is used for sign-in purposes while User.Read.All is not. When I restored User.Read, my problem was solved.
Quite the unintuitive solution.
I have set up a new web app to be able to use the Oauth2 V2 authorization endpoint. I defined the app in https://apps.dev.microsoft.com/
If I want to obtain a new authorization token, following instructions in
https://blogs.msdn.microsoft.com/richard_dizeregas_blog/2015/09/04/working-with-the-converged-azure-ad-v2-app-model/
I get the following error on the login page:
Sorry, but we’re having trouble signing you in.
We received a bad request.
Additional technical information:
Correlation ID: eb9c2331-32bd-45a9-90d1-e9105f0bfa87
Timestamp: 2016-05-22 18:10:48Z
AADSTS70011: The provided value for the input parameter 'scope' is not valid. The scope https://graph.microsoft.com/Calendar.Read is not valid.
The scope is taken from an example in :
https://github.com/Azure/azure-content/blob/master/articles/active-directory/active-directory-v2-scopes.md
So I imagine it is a valid scope.
In v1 of the OAuth2 protocol, it was necessary to configure access to APIs in the Azure AD of my tenant, prior to using them. So I attempted to do so for the new application.
Attempting to do so, the Azure application management reports an error:
{
"message":"This request has a value that is not valid.",
"ErrorMessage":"This request has a value that is not valid.",
"httpStatusCode":"InternalServerError","operationTrackingId":null,"stackTrace":null,"Padding":null
}
What is missing to be able to use the new authorization endpoint ?
The documentation contains a typo if states calendar.read. It must be calendars.read:
private static string[] scopes = {
"https://graph.microsoft.com/calendars.readwrite"};
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(scopes, additionalScopes, clientId, redirectUri, UserIdentifier.AnyUser, null);