How to set custom claims to aad token using C# code - c#

I have a webapi which generates aad token and I have written token generation logic in Get() method in webapi.
I'm able generate aad jwt token from webapi get() method but, now I want to include some custom claims into the token.
How can I set custom claims to aad token using c#.
I have used below code for generating aad token.
var authenticationContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.windows.net/" + ConfigurationManager.AppSettings["TenantID"].ToString());
var credential = new ClientCredential(clientId: ConfigurationManager.AppSettings["ClientID"].ToString(), clientSecret: secret);
var result = await authenticationContext.AcquireTokenAsync(
ConfigurationManager.AppSettings["Resource"].ToString(),
credential
).ConfigureAwait(false);
Kindly share any sample c# code to set custom claims to aad token generated from above code .
Note: I want to set a new custom claim for aad token where custom claim value obtained from external logic.
Update 1:
Looks like below post may be useful.
https://www.rahulpnath.com/blog/azure-ad-custom-attributes-and-optional-claims-from-an-asp-dot-net-application/
I tried below following above post.
Generated jwt token to call Graph API. But I got blocked at below code.
var dictionary = new Dictionary<string, object>();
dictionary.Add(employeeCodePropertyName, employee.Code);
//Here I can't use graphApiClient.Users because, I don't have any user info on my jwt token. It will be just Access token which as details related to aad application.I want to update extension attribute which is present in OptionalClaims -> Access Token of AAD Application Manifest.
await graphApiClient.Users[employee.EmailAddress]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});
How to update extension claim attribute present in access token of optional claims . I want to update through c# code.
How to do that. Kindly suggest.
Update 2:
I want to use code similar to below for updating custom extension claim attribute present in optional claims of azure ad app but UpdateAsync is not working.
await graphApiClient.Application[clientid]
.Request()
.UpdateAsync(new Application()
{
AdditionalData = dictionary
});
At UpdateAsync(), I'm getting issue as below
Specified HTTP method is not allowed for the request
Kindly let me know the solution to add custom user defined claim to azure ad access token.
Note: Since, I want to update access token ,there will be not be any user info on access token. So, graphApiClient.Users[employee.EmailAddress] won't work, as access token will not have any user info like EmailAddress
I have created extension claim attribute in azure ad ap manifest as below
I want to update extension claim attribute extension_clientid_moviename value dynamically with value obtained from external source through code. How to do that. Kindly suggest.
Update 3:
I have tried below code to update extension claim attribute present in Optional claims ID Token.
await graphApiServiceClient.Users["abcd#hotmail.com"]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});
I am getting error as below
Code: Request_ResourceNotFound\r\nMessage: Resource '' does not exist or one of its queried reference-property objects are not present.

That is because you are using OAuth 2.0 client credentials flow to get access token :
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
That flow is commonly used for server-to-server interactions without immediate interaction with a user. So you can't find user information in access token .
The document you provided is adding User's extension property , update that value via Microsoft Graph :
await graphApiClient.Users[employee.EmailAddress]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});
That email is not from access token , you should manually provide the user's email who the api wants to update the properties value .
But since the property is a User's extension property , you can get the value from claims in ID token when user login with Azure AD in your client app . That claim won't include in access token which issued using client credential flow .

Regarding the issue in Update 3, we can not use the guest user email here. We should use ObjectId instead. That's why you got Request_ResourceNotFound error.
await graphApiServiceClient.Users["ObjectId"]
.Request()
.UpdateAsync(new User()
{
AdditionalData = dictionary
});

Related

Get refresh token additionally to access token with Microsoft.Identity.Client

I use several properties like tenant id, client id, client secret, redirect uri and an authorization code generated for a user. I need to get the access and refresh token, but with the API that don't return anything like a refresh token. I need a refresh token additionnally to the access token and the expire in time.
I use this following code:
ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions();
options.ClientId = clientId;
options.TenantId = tenantId;
options.ClientSecret = clientSecret;
options.RedirectUri = redirectUri;
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.
CreateWithApplicationOptions(options);
IConfidentialClientApplication app = builder.Build();
AcquireTokenByAuthorizationCodeParameterBuilder acquireTokenBuilder =
app.AcquireTokenByAuthorizationCode(ServiceConstants.ALL_SCOPE_AUTHORIZATIONS.Split(' '), authorizationCode);
AuthenticationResult result = await acquireTokenBuilder.ExecuteAsync();
string accessToken = result.AccessToken;
// NO string refreshToken = result.RefreshToken
Its very strange because in several example, I see the RefreshToken available in AuthenticationResult, but not in mine. Do you know why ? And how I can get the refresh token plz ?
Because after that I will need to refresh the access token when will expire and I only have the access token, tenant id, client id, secret (or certificate) and redirect uri. BTW How to regenerate it after access token expiration ?
thank a lot and best regards
Adrien
You need to check what is passed as ServiceConstants.ALL_SCOPE_AUTHORIZATIONS in both /authorize and /token requests. The list of scopes should contain offline_access scope as it tells Azure that your application will need a refresh token for extended access to resources.
The refresh token will have a longer lifetime than the access token, therefore whenever your access token expires you will be able to call the /token endpoint again providing the previously received refresh token and using the parameter grant_type=refresh_token.
I tried to reproduce the same in my environment and got the results like below:
I created an Azure AD Application and added API permissions:
Note that: To get refresh token make sure to grant offline_access API permission in your Azure AD Application and include it in the scope while generating access token.
I generated access and refresh token using below parameters in Postman:
GET https://login.microsoftonline.com/TenantId/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default offline_access
grant_type:authorization_code
redirect_uri:RedirectUri
code:code
To get this in your code you can include the below line:
refreshToken = result.RefreshToken
To refresh the access token, I used the parameters like below:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientId
grant_type:refresh_token
refresh_token:refreshtoken
client_secret:ClientSecret
Sample Code:
AzureADApp.AcquireTokenByRefreshToken(RefreshToken, scope) .ExecuteAsync();
var refreshedAccessToken = result.AccessToken;

How to add custom claims to Jwt Token in OpenIdConnect in .Net Core

Just like AzureAD we have our own custom Firm ActiveDirectory which we are connecting from UI as well as API for Authentication in .NetCore using OpenIdConnect (AddOpenIdConnect extension method).
In my use case after authentication on UI side, I need additional application specific claims from my custom database which I am adding "OnTokenValidated" - this is needed for hiding or exposing the UI elements based on Roles and Claims.
OnTokenValidated = async ctx =>
{
//Get user's immutable object id from claims that came from Azure AD
string oid = ctx.Principal.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
//Get EF context
var db = ctx.HttpContext.RequestServices.GetRequiredService<AuthorizationDbContext>();
//Check is user a super admin
bool isSuperAdmin = await db.SuperAdmins.AnyAsync(a => a.ObjectId == oid);
if (isSuperAdmin)
{
//Add claim if they are
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "superadmin")
};
var appIdentity = new ClaimsIdentity(claims);
ctx.Principal.AddIdentity(appIdentity);
}
}
Now after token validation on API side again I have to call the custom database to fetch application specific roles. Is it possible to include these roles in JWT token itself so on API side all roles and claims(AD + Custom DB) are present. Or any other way by which I don't have to call CustomDB again in API.
Adding custom claims to access tokens is a capability of the Authorization Server (AS) and not all of them support this - though they should since it is an important feature. If you can say exactly what provider you are using I may be able to tell you whether it is possible.
These are the factors to think about:
Find out if the AS can reach out and get custom claims at the time of token issuance as in this Curity article.
Be careful to not return detailed JWTs to internet clients and aim to keep them within your back end instead. Opaque tokens can help with this as in this other Curity article
If custom claims are not possible for your provider then you can look them up in your API(s) when an access token is first received and then cache the claims in memory. This leads to more complex API code but is necessary for some providers.

How to get the oauth refresh token?

I have a web app which utlizes Oauth and is unable to reuse the authToken due to the error below.
{"AADSTS70002: Error validating credentials. AADSTS54005: OAuth2 Authorization
code was already redeemed, please retry with a new valid code or use an
existing refresh token.\r\nTrace ID: 30c342a7-f16a-4a05-a4a8-
c7ee2c722300\r\nCorrelation ID: 3a5c99d1-ca1c-4cd7-bd36-
cce721bf05b6\r\nTimestamp: 2018-11-21 00:26:18Z"}
I'm told this is a know issue/update here and here.
...okay, fine so now I'm trying to get the refresh token so I can regenerate my access token but I'm having trouble getting something to work.
I have tried the ones below:
https://learn.microsoft.com/en-us/bingads/shopping-content/code-example-authentication-oauth - this one does not seem to work and throws an exception when I try to get the accesstoken or refresh token. stating that one or more errors have occured.
https://auth0.com/docs/api/authentication#authorization-code-pkce- - but does not return the refresh token. Could this be because I don't have the code_verifier? If so, how would I get that?
Authorization Code (PKCE) Image
Below is a code sample which I am using - problem here is that I can only use this once and once It has been redemed I cannot retrive it silently as it no longer exists in the cache.
ClientCredential clientcred = new ClientCredential(Constants.ClientId, Constants.AppKey);
TokenCache TC = new TokenCache();
AuthenticationContext AC = new AuthenticationContext(Constants.Authority, TC);
//Set token from authentication result
AuthenticationResult authenticationResult = await AC.AcquireTokenByAuthorizationCodeAsync(
Constants.Code,
new Uri(Constants.postLogoutRedirectUri + "Index"), clientcred);
return authenticationResult.AccessToken;
You need to call OAuth2 authorize endpoint with offline_access scope to get refresh token.
You should call AcquireTokenByAuthorizationCodeAsync only once when you receive authorization code and should not use the result. azure ad sample
You need to call AcquireTokenSilently when you want to get access token. azure ad sample
This azure ad sample use a TokenCache implementation by user id.
Authorize request
Token request
Good luck!

Auth0Client.LoginAsync() v4.x doesn't return refresh token in a C# WPF app

Here's my code:
var auth0 = new Auth0Client(
Properties.Settings.Default.auth0Domain,
Properties.Settings.Default.auth0ClientID);
var handle = new WindowInteropHelper(this).Handle;
var windowWrapper = new WindowWrapper(handle);
var user = await auth0.LoginAsync(
owner: windowWrapper,
scope: "openid profile",
withRefreshToken: true);
The return value contains a null refresh token. I also tried setting the device parameter to an arbitrary value, but that didn't help. I recently switched from Auth0 v3.x to v4.x. Auth0 v3.x returned a refresh token. Did this break in v4.x? If not, what do I need to do to get a refresh token?
What kind of identity providers are you using? - I tried this with Microsoft Live, and a Google account and that worked.
When you Login/Authenticate, do you get any other fields filled in the "user" result (without giving away any details/tokens).
Can you try like this i.e. not use "profile" in the scope.
var user = await auth0.LoginAsync(
owner: windowWrapper,
scope: "openid offline_access",
withRefreshToken: true,
device: "my-device");
or
var user = await auth0.LoginAsync(
owner: windowWrapper,
scope: "openid",
withRefreshToken: true,
device: "my-device");
Internally it appends "offline_access" onto the scope name if you have withRefreshToken=true that's why either of the above works.
You also need to supply an "id" individually for each of your devices so they can each be assigned their own refresh token.
When a refresh token has been issued, it will show up in the Dashboard -> in Users -> choose the User Authentication identity used to login -> then in User Details -> choose the Devices tab ... this will show how many Number of Refresh Tokens have been issued to your device.
As mentioned in the documentation, you shouldn't keep requesting a refresh_token....only do that when they've expired - (see how I requested more than I needed) - perhaps you hit the throttling limits on requesting refresh tokens...and it simply returns a "null" token instead...but still let your authentication succeed without error....a possibility?
Here's the documentation on refresh token (you probably know this already as you must have used it in v3).
https://auth0.com/docs/tokens/refresh-token#revoke-a-refresh-token-in-the-dashboard

Exchanging a google idToken for local openId token c#

I am using this github project https://github.com/openiddict/openiddict-core which is great. But I am stuck as to what the procedures should be, or how to implement them, when the user uses an external identity provider, for this example, I will use google.
I have an angular2 app running, with an aspnet core webAPI. All my local logins work perfectly, I call connect/token with a username and password, and an accessToken is returned.
Now I need to implement google as an external identity provider. I have followed all the steps here to implement a google login button. This opens a popup when the user logins in. This is the code I have created for my google button.
// Angular hook that allows for interaction with elements inserted by the
// rendering of a view.
ngAfterViewInit() {
// check if the google client id is in the pages meta tags
if (document.querySelector("meta[name='google-signin-client_id']")) {
// Converts the Google login button stub to an actual button.
gapi.signin2.render(
'google-login-button',
{
"onSuccess": this.onGoogleLoginSuccess,
"scope": "profile",
"theme": "dark"
});
}
}
onGoogleLoginSuccess(loggedInUser) {
let idToken = loggedInUser.getAuthResponse().id_token;
// here i can pass the idToken up to my server and validate it
}
Now I have an idToken from google. The next step on the google pages found here says that I need to validate the google accessToken, which I can do, but how do I exchange the accessToken that I have from google, and create local accessToken which can be used on my application?
Edit: this answer was updated to use OpenIddict 3.x.
The next step on the google pages found here says that i need to validate the google accessToken, which i can do, but how do i exchange the accessToken that i have from google, and create local accessToken which can be used on my application?
The flow you're trying to implement is known as assertion grant. You can read this other SO post for more information about it.
OpenIddict fully supports custom grants, so this is something you can easily implement in your token endpoint action:
[HttpPost("~/connect/token"), Produces("application/json")]
public IActionResult Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest();
if (request.GrantType == "urn:ietf:params:oauth:grant-type:google_identity_token")
{
// Reject the request if the "assertion" parameter is missing.
if (string.IsNullOrEmpty(request.Assertion))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The mandatory 'assertion' parameter was missing."
}));
}
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token and/or an access token.
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
// Manually validate the identity token issued by Google, including the
// issuer, the signature and the audience. Then, copy the claims you need
// to the "identity" instance and call SetDestinations on each claim to
// allow them to be persisted to either access or identity tokens (or both).
//
// Note: the identity MUST contain a "sub" claim containing the user ID.
var principal = new ClaimsPrincipal(identity);
foreach (var claim in principal.Claims)
{
claim.SetDestinations(claim.Type switch
{
"name" => new[]
{
Destinations.AccessToken,
Destinations.IdentityToken
},
_ => new[] { Destinations.AccessToken },
});
}
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
Note that you'll also have to enable it in the OpenIddict server options:
services.AddOpenIddict()
// ...
.AddServer(options =>
{
// ...
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_identity_token");
});
When sending a token request, make sure to use the right grant_type and to send your id_token as the assertion parameter, and it should work. Here's an example with Postman (for Facebook access tokens, but it works exactly the same way):
That said, you have to be extremely careful when implementing the token validation routine, as this step is particularly error-prone. It's really important to validate everything, including the audience (otherwise, your server would be vulnerable to confused deputy attacks).

Categories

Resources