I'm considering using the JWT audience field to implement role-based authorization in my app.
So I'd have ServiceA which requires 'RoleA' audience to be present, ServiceB requires 'RoleB' etc. Then when I issue the JWT, I include the appropriate audience(s).
Relevant section from the JWT draft spec:
The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected... The interpretation of audience values is generally application specific.
So it appears that would work but since I'm new to JWT I'm wondering: is role-based authorization an appropriate use case for the audience field? Or should I roll my own logic using a payload with custom roles array etc?
Thanks
I understand audience rather then list of consumers/applications who can authorize the user.
In my application I put roles into own array in the payload. For example like that.
{
"sub": 1234567890,
"exp": 9876543210,
"name": "John Doe",
"roles": ["USER", "EDITOR"]
}
On the server I am authorized using spring security and user loaded from "sub".
And on the client I can use these roles to show proper buttons and fields.
Related
I am configuring an OIDC-based SSO flow in Azure AD B2C using custom policy to allow users to login to downstream applications with their federated identity provider's (IdP) credentials. Custom policy is used to allow some complex business logic to be run prior to providing the token to the downstream applications.
The flow is correctly redirecting users to the external IdP for login and ultimately back to my downstream applications with associated claims. However, there is a custom claim that is only available in the access token received by B2C from the external IdP (not the ID token), and I can't figure out how to retrieve this claim from the access token to be used in the B2C user journey and ultimately provided with all the other claims to the downstream applications.
I can see that B2C does receive both the ID and access tokens by reviewing Application Insights logs (sample output):
"TESTtechnicalprofile": {
"ContentType": "Jwt",
"Created": "2022-10-15T07:37:45.8678974Z",
"Key": "TESTtechnicalprofile",
"Persistent": true,
"Value": "eyJhb..."
},
"TESTtechnicalprofileaccess_token": {
"ContentType": "Unspecified",
"Created": "2022-10-15T07:37:45.8678974Z",
"Key": "TESTtechnicalprofileaccess_token",
"Persistent": false,
"Value": "eyJhb..."
},
And general format of the payloads of the tokens is as follows:
ID Token
{
"<name of custom claim I can retrieve>": "custom claim value",
"iss": ...,
...
}
Access Token
{
"<name of custom claim I cannot retrieve>": "custom claim value",
"iss": ...,
...
}
I can successfully retrieve claims from the ID token by mapping from the partner claim type:
<OutputClaim ClaimTypeReferenceId="email" PartnerClaimType="<name of claim containing email in ID token>" />
However this same method doesn't work for claims in the access token. If I reference the name of the custom claim in the access token in PartnerClaimType then B2C omits the claim (presumably because it fails to retrieve it).
I've tried retrieving the access token itself as a claim using the method described here and that works (token in claim matches token seen in Application Insights logs), however I'm not sure if it's possible to decode this token in B2C policy and subsequently pull claims from it (or even if one would want to do that).
While I could let the downstream applications retrieve what they need from the access token, I have business logic in my user journey that needs this claim prior to providing the final token to the applications.
Following up here for anyone else trying to do the same, according to Microsoft Support it isn't possible to extract a claim from an access token in B2C policy. I ended up crafting a workaround involving calling an external REST API from B2C to retrieve the needed info for the user journey.
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
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!
So I know how to create tokens and how to read tokens but I am running into an issue with getting the User_Metadata from the Users I created in Auth0 (without login them in from my application).
What I am trying to do is this:
User some where with a device logs into Auth0 and generates a JWT
Token
User now calls my API and passes Bearer with token in header
I read Bearer and Authenticate that the token is good. I then want to
pull the user information from the token to use to make sure they
have rights to do something.
I am not wanting a 2nd database that holds user information that they will need to log into my API so I know who they are. I just want to be able to use the JWT Token to get that information. Right now when I create a token I have this in the Payload:
{
"iss": "https://.....",
"sub": "RTMLeICuyL1kyeQN#clients",
"aud": "https://.....",
"exp": 1494031764,
"iat": 1493945364,
"scope": ""
}
If I go to Auth0 User Details tab I can see the user and the user_metadata and app_metadata that I want to return but not sure how to get it. Thanks for any help.
I was not able to get the User Profile data from Auth0 to come in on the JWT Token but I was able to use the client scopes in Auth0 to create the scopes needed to do Authorization. This is still not the best answer but it will allow me to determine if someone has rights to view that object and records.
I will go into more details as I write up an example.
I'm using the Azure AD Basic tier with an ASP.NET Core API, I've followed the RBAC sample. I've set up an application with roles in my manifest like so:
appRoles": [
{
"allowedMemberTypes": [ "User" ],
"displayName": "Read Device",
"id": "b2e6f6c2-c3d5-4721-ad49-0eea255ccf45",
"isEnabled": true,
"description": "Can read a device.",
"value": "read_device"
},
...
]
I've setup my API to use the UseJwtBearerAuthentication middleware like so:
application.UseJwtBearerAuthentication(
new JwtBearerOptions()
{
AuthenticationScheme = "Azure Active Directory",
Authority = options.Authority,
Audience = options.ClientId,
TokenValidationParameters = new TokenValidationParameters()
{
RoleClaimType = "roles",
ValidateIssuer = false
}
})
I've given my user the above 'Read Device' role:
I'm using Swagger UI to make the call to get the auth token. It calls the following URL:
https://login.microsoftonline.com/[Tenant].onmicrosoft.com/oauth2/authorize?
response_type=token
&redirect_uri=http%3A%2F%2Flocalhost%3A5100%2Fswagger%2Fo2c.html
&realm=-
&client_id=[Client ID]
&scope=http%3A%2F%2Fschemas.microsoft.com%2Fws%2F2008%2F06%2Fidentity%2Fclaims%2Frole
&state=oauth2
&resource=[Client ID]
I suspected that I am not passing the correct values to the scope parameter, so I have tried asking for every scope I can think of:
&scope=openid
%20email
%20profile
%20offline_access
%20user_impersonation
%20roles
%20http%3A%2F%2Fschemas.microsoft.com%2Fws%2F2008%2F06%2Fidentity%2Fclaims%2Frole
%20read_device
If I set "groupMembershipClaims": "All" in my manifest I can see group claims but I want roles instead. I'm able to login to call my API, however I never get any roles back in my JWT token, so I'm unable check the users role. What am I doing wrong?
It turns out I needed to request an id_token instead of a token. An id_token contains extra claims/scopes/resources about the user. I also needed to provide a nonce parameter containing a new random GUID on every request. Thus, I ended up with the following URL:
https://login.microsoftonline.com/[Tenant].onmicrosoft.com/oauth2/authorize?
response_type=id_token
&client_id=[Client ID]
&redirect_uri=http%3A%2F%2Flocalhost%3A5000%2F
&nonce=9ac5ad8d-df44-48e6-9bd6-e72743b3625c
If you are want to enable the role be assigned to users or groups(allowedMemberTypes=User) :
If you want to perform authorization using role claims , you could
follow the steps in this code sample , you could find the roles
claim is in the id_token .
If you want to make a client app to call your web api , when user
sign in ,app could check the access rules based on the role
claim,
you could use delegate flow(OAuth Authorization Code Grant,Implicit
Grant Flow..),roles claim is in the access_token ;
If you want to specify the role be assigned to client applications(allowedMemberTypes=Application), you could use OAuth Client Credential Flow ,appRoles of resource app/api that are assigned to the client app, and you will find the roles claim in the access_token ,check the detail steps from here.
Please click here for more details .
In my case I had mistakenly configured the App Registration to emit Security Groups as roles claims, thus overwriting the App Roles from the manifest. Removing the optional groups claim and logging back in correctly emitted the App Roles names in the roles claim of the id_token.