I have setup a SignUp with email invitation flow as described here
The id_token_hint looks like this:
{
"alg": "RS256",
"kid": "00BFDFB35FF5994E543B5D8CE74B37FC5E702294",
"x5t": "AL_fs1_1mU5UO12M50s3_F5wIpQ",
"typ": "JWT"
}.{
"name": "Name",
"email": "email#domain.com",
"roles": [
"role1",
"role2",
"role3"
],
"nbf": 1651067986,
"exp": 1651068286,
"iss": "xx",
"aud": "xx"
}.[Signature]
and I try to extract the roles to a stringCollection Claim, so that I can use it later. The claim is defined like this:
<ClaimType Id="InvitationRoles">
<DisplayName>Invitation Roles</DisplayName>
<DataType>stringCollection</DataType>
<UserHelpText>Invitation Roles</UserHelpText>
</ClaimType>
I added the following to the IdTokenHint_ExtractClaims ClaimsProvider TechnicalProfile:
<OutputClaim ClaimTypeReferenceId="InvitationRoles" PartnerClaimType="roles"/>
and this to the RelyingParty TechnicalProfile PolicyProfile:
<InputClaim ClaimTypeReferenceId="InvitationRoles" PartnerClaimType="roles" />
But I only get the first value of the array shown in the Application Insights Debug Logging:
Claims
InvitationRoles: [role1]
ReadOnlyEmail: email#domain.com
email: email#domain.com
Do I miss something or is this not supported?
Please check if given references can narrow down the issue.
Please check if this > Default value for stringCollection in Azure AD B2C custom policy - Microsoft Q&A can give idea to work around
Define the claims schema.
Add the claims transformation rule.
To the required Technical Profile, add the string claim as the
output claim and the claims transformation rule to transform it to a
stringCollection claim.
Finally add the claim as output claim
You can extract claims from id_token_hint using the instructions and
sample mentioned here - AAD-b2c-id-token-hint
Note: But in some cases The token's retrieved from B2C do not contain all
the information about the user and its claim or attributes. You may
need to use the Graph API to query the user for its information .
You may check this c# - Azure B2C How to retrieve Built-In User Claims/Attributes - Stack Overflow On how to get array of them programmatically.
The output you have looks like extractedItem from getsingleitemfromstringcollection claim stransformation where string ClaimTypes that are produced after this ClaimsTransformation gets the first item in the collection.
Please check if you can make use of AddItemToStringCollection to add to other roles and then output the extracted item:
example-of-additemtostringcollection
References:
azure ad b2c - How to extract multiple values from id_token_hint
into specific claims? - Stack Overflow
azure ad b2c - id_token_hint parameter does not contain an accepted issuer - Stack Overflow
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.
I have a question regarding keycloak and Azure Ad b2c. Both offres authentication with different identity providers. Main difference I can see is in authorization. With Keycloak I can easily manage claims and roles of users via admin portal. In Azure B2c it looks very hard (setting custom policies) maintaining all the data via custom created app. Am I missing something? Relying app is strongly cloud native (Azure of course) and thats why I'm thinking about azure b2c. Relying app will strongly use roles and claims also.
What do you think about these two identity solutions (pros and cons).
What is the easiest way to manage implemented (by custom policies) user claims and roles in azure b2c? (do I have to write separate management app by myself or there is easiesy solution?)
Edit: first conclusion is that right nów azure requires separate app for connecting to graphapi (via rest or using sdk). Keycloak has it built in. Any pros of using b2c in this case (for c# dev)?
You don't have to use custom policies to use custom claims. Below an example :
On your B2C tenant, go to Azure AD B2C > Manage > User attributes and create a custom attribute; here a client ID attribute :
Then Azure AD B2C > Policies > User flows and create a flow, for example "Sign In" :
Use the recommended flow :
During the flow's creation, at the last steps, you can specific what claim will be included in the returned jwt token on Sign in; select our custom attribute Client ID :
Then when I login into my application using a B2C account, I have the below JWT token returned :
JWT token's payload, base64 decoded :
{
"iss": "https://mytenant.b2clogin.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/v2.0/",
"exp": 1664181788,
"nbf": 1664178188,
"aud": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"oid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"extension_ClientID": 1,
"name": "myUserName",
"emails": [
"user#email.com"
],
"tfp": "B2C_1_SI",
"nonce": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"scp": "user_impersonation",
"azp": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ver": "1.0",
"iat": 1664178188
}
I have set the "extension_ClientID": 1 using the msGraph API but this could also be done using a SignUp flow.
I want to use the AAD B2C Github identity provider to authorize users in my app. To create a user I need at least get an email from it - but I get nothing. I did set up everything according to docs and I can see in the AAD B2C Users list that Name is set up correctly for a new user, but User Principal Name where email should be is null
Here is JWT answer
{
"typ": "JWT",
"alg": "RS256",
"kid": "X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk"
}.{
"exp": 1611879546,
"nbf": 1611875946,
"ver": "1.0",
"iss": "https://apichat.b2clogin.com/4d39cd56-4c18-4bc7-aaa8-36bf91191c8c/v2.0/",
"sub": "dfe38752-113e-4431-b1bd-23dd53119369",
"aud": "341eea81-859c-485c-baea-2cc9f75f6512",
"nonce": "defaultNonce",
"iat": 1611875946,
"auth_time": 1611875946,
"idp_access_token": "c5c79a8f49c44575cf127fc3c64aaa5710a0a465",
"idp": "github.com",
"tfp": "B2C_1_susi_debug"
}.[Signature]
What do I missing?
Added
After some studying, I have a suspicion that the Github provider here either does not have the required scopes or mappings. I don't see any ways to add it so far. Potentially that might be solved by a generic OpenID Connect provider but Github does not support well-known/openid-connect-discovery and I have no option to manually set endpoints in AAD B2C.
So far I don't see any way to connect GitHub to my AAD B2C and get that darn email - why the biggest cloud platform does not fully support the biggest dev repository when they have the same owner is beyond my understanding.
Ok, the solution I found looks like that
Set Display Name and Identity Provider Access Token in Application Claims of your User Flow
On GitHub auth you will get name aka username and idp_access_token aka token
That's allow us to call github user api curl -u username:token https://api.github.com/user
By default user api returns public user profile, which might not have a set email
curl -u username:token https://api.github.com/user/emails will return all user associated emails
We need the primary one
{
"email": "***#gmail.com",
"primary": true,
"verified": true,
"visibility": "public"
}
I have an Angular Application which is authenticated using AAD B2C. This talks to a .Net Core API using an access token.
My problem is that I am not receiving a User Principal Name (upn) in my access token.
I have been adding additional "Application claims" like "Given name" and "Surname" and these appear in my access token just fine! Therefore, I believe that my scopes (openid, profile, email) are set correctly and that this in theory is working.
I believe since I am using version 1.0 of the token, that I do not need to configure an any additional claims in my application manifest. My user is a standard AD user not a guest.
The following document states that the upn claim should be included in the v1.0 tokens:
https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims
What am I doing wrong?!
Decoded Access Token:
{
"iss": "https://(my-tenant-name).b2clogin.com/(guid)/v2.0/",
"exp": (number),
"nbf": (number),
"aud": "(guid)",
"oid": "(guid)",
"sub": "(guid)",
"name": "My Name",
"given_name": "Given name",
"family_name": "Surname",
"country": "Norge",
"tfp": "B2C_1_signupsignin2",
"nonce": "(guid)",
"scp": "basic",
"azp": "(guid)",
"ver": "1.0",
"iat": (number)
}
UPN is not returned in AAD B2C tokens because it is an irrelevant random string that is set.
Rather AAD B2Cs unique name is stored in signInNames attribute, and returned in your token as email or username.
The doc you linked is for AAD, and irrelevant to AAD B2C. These are two seperate token issuer services. Select in your User Flow Application Claims to return “email addresses”.
https://learn.microsoft.com/en-us/azure/active-directory-b2c/user-flow-overview#email-address-storage
At this time, apps that support both personal accounts and Azure AD (registered through the app registration portal) cannot use optional claims. However, apps registered for just Azure AD using the v2.0 endpoint can get the optional claims they requested in the manifest.
To achieve UPN Claim in the token, use B2C Custom Policy.
Refer this link for the starter pack: https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack
Add below claim to the TechnicalProfile AAD-UserReadUsingObjectId and in the Relying Party Policy (Eg: SignUpOrSignin.xml):
<OutputClaim ClaimTypeReferenceId="userPrincipalName" />
As per the code you have provided we observed you are using v2 endpoint( "iss": "https://(my-tenant-name).b2clogin.com/(guid)/v2.0/")
As per the document if you see iss:If the token was issued by the v2.0 endpoint, the URI will end in /v2.0.
In the V2 endpoint you need to make a request explicitly for UPN claim.
In v1.0 endpoint they are returned by default but v2.0 made smaller tokens so they made it optional.
Please go through the following links for more understanding. https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims How to add optional claims in application manifest https://learn.microsoft.com/en-us/azure/active-directory/develop/reference-app-manifest
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!