How to set Claims from ASP.Net OpenID Connect OWIN components? - c#

I have questions upon using the new ASP.Net OpenID Connect framework while adding new Claims during the authentication pipeline as shown in the code below. I'm not sure just how much 'magic' is happening behind the scenes. I think most of my questions center around not knowing much about OWIN authentication middleware as opposed to OpenID Connect.
Q1. Should I be manually setting HttpContext.Current.User and Thread.CurrentPrincipal from OwinContext.Authentication.User?
Q2. I want the ability to add object types to claims like I used to with System.IdentityModel.Claims.Claim. The new System.Security.Claims.Claim class only accepts string values?
Q3. Do I need to use the new SessionSecurityToken wrapper for my ClaimsPrincipal in System.Security.Claims.CurrentPrincipal for serializing into a cookie - I am using app.UseCookieAuthentication(new CookieAuthenticationOptions()); but now sure what that does exactly in terms of maintaining any additional claims I added during SecurityTokenValidated event?
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = clientId,
Authority = authority,
PostLogoutRedirectUri = postLogoutRedirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
SecurityTokenValidated = (context) =>
{
// retriever caller data from the incoming principal
var UPN = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Name).Value;
var db = new SOSBIADPEntities();
var user = db.DomainUser.FirstOrDefault(b => (b.EntityName == UPN));
if (user == null)
{
// the caller was not a registered user - throw to block the authentication flow
throw new SecurityTokenValidationException();
}
var applicationUserIdentity = new ClaimsIdentity();
applicationUserIdentity.AddClaim(new Claim(ClaimTypes.Name, UPN, ""));
applicationUserIdentity.AddClaim(new Claim(ClaimTypes.Sid, user.ID.ToString(CultureInfo.InvariantCulture)));
var applications =
db.ApplicationUser
.Where(x => x.ApplicationChild != null && x.DomainUser.ID == user.ID)
.Select(x => x.ApplicationChild).OrderBy(x => x.SortOrder);
applications.ForEach(x =>
applicationUserIdentity.AddClaim(new Claim(ClaimTypes.System, x.ID.ToString(CultureInfo.InvariantCulture))));
context.OwinContext.Authentication.User.AddIdentity(applicationUserIdentity);
var hasOutlook = context.OwinContext.Authentication.User.HasClaim(ClaimTypes.System, "1");
hasOutlook = hasOutlook;
HttpContext.Current.User = context.OwinContext.Authentication.User;
Thread.CurrentPrincipal = context.OwinContext.Authentication.User;
var usr = HttpContext.Current.User;
var c = System.Security.Claims.ClaimsPrincipal.Current.Claims.Count();
return Task.FromResult(0);
},
}
}
);
}

Is there a specific reason for which you are adding a new ClaimsIdentity?
The simplest way of doing what you are aiming at is to retrieve the ClaimsIdentity that was generated by validating the incoming token, via ClaimsIdentity claimsId = context.AuthenticationTicket.Identity; once you have it, just add claims to it. The rest of the middleware will take care of serializing it in the session cookie along with everything else, place the result in the current ClaimsPrincipal, and all those other things you appear to be trying to do manually.
HTH
V.

When performing the token validation you can SignIn with new Identity:
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> n)
{
var claimIdentity = new ClaimsIdentity(n.AuthenticationTicket.Identity);
// Custom code...
claimIdentity.Claims.Append(new Claim("TEST","1234"));
n.OwinContext.Authentication.SignIn(claimIdentity);
return Task.FromResult(0);
}
Another option make the assignment directly, not working for me:
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> n)
{
var claimsPrincipal = new ClaimsPrincipal(n.AuthenticationTicket.Identity);
// Custom code...
// TEST:
n.OwinContext.Response.Context.Authentication.User = claimsPrincipal;
n.OwinContext.Request.User = claimsPrincipal;
n.OwinContext.Authentication.User = claimsPrincipal;
return Task.FromResult(0);
}

Related

How to persist newly added claims to existing HttpContext user in Asp.Net Core 3.1

I am new to .Net Core and have configured authentication as follow in Startup.cs file -
public void ConfigureServices(IServiceCollection services)
{
// To be able to access HttpContext
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(o => o.LoginPath = "/login");
// Most of the code removed for brevity
}
After user login, we authenticate the user by -
public static async Task AuthenticateUserAsync(HttpContext httpContext, AuthorizedUser authorizedUser)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, authorizedUser.UserUid.ToString()),
new Claim(CustomClaimTypes.CompanyGuid, authorizedUser.CompanyUid.ToString())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
// Refreshing the authentication session should be allowed.
//AllowRefresh = <bool>,
// The time at which the authentication ticket was issued.
IssuedUtc = DateTimeOffset.UtcNow,
// The time at which the authentication ticket expires. A
// value set here overrides the ExpireTimeSpan option of
// CookieAuthenticationOptions set with AddCookie.
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
// Whether the authentication session is persisted across
// multiple requests. When used with cookies, controls
// whether the cookie's lifetime is absolute (matching the
// lifetime of the authentication ticket) or session-based.
IsPersistent = false,
//RedirectUri = <string>
// The full path or absolute URI to be used as an http
// redirect response value.
};
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
In some service class, we retrieve portal information and tried to save it in claims as follow -
public async Task AddPortalToCurrentUserClaimsAsync(Guid companyUid, Guid userUid)
{
var portal = await _unitOfWork.Portals.All().FirstOrDefaultAsync(p => p.CompanyUid == companyUid && p.UserUid == userUid).ConfigureAwait(false);
if (portal == null) return;
var claims = new List<Claim>
{
new Claim(CustomClaimTypes.PortalId, portal.Id.ToString()),
new Claim(CustomClaimTypes.PortalName, portal.Name)
};
var claimsIdentity = new ClaimsIdentity(claims);
_httpContextAccessor.HttpContext.User.AddIdentity(claimsIdentity);
}
When I tried to retrieve this claim in further requests, I get null.
var portalId = _httpContextAccessor.HttpContext.User.FindFirst(CustomClaimTypes.PortalId);
var portalName = _httpContextAccessor.HttpContext.User.FindFirst(CustomClaimTypes.PortalName);
How I can get these newly added claims to persist in further requests?
I read from the article that share
To create a cookie holding user information, construct a
ClaimsPrincipal. The user information is serialized and stored in the
cookie. SignInAsync creates an encrypted cookie and adds it to the
current response. If AuthenticationScheme isn't specified, the
default scheme is used.
So, you cannot add claims to already created ClaimsPrincipal as claims have already been stored in cookies.
The solution that work for me was to create new ClaimsPrincipal and create new cookie as -
public async Task AddPortalToCurrentUserClaimsAsync(Guid companyUid, Guid userUid)
{
var portal = await _unitOfWork.Portals.All().FirstOrDefaultAsync(p => p.CompanyUid == companyUid && p.UserUid == userUid).ConfigureAwait(false);
if (portal == null) return;
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, userUid.ToString()),
new Claim(CustomClaimTypes.CompanyGuid, companyUid.ToString()),
new Claim(CustomClaimTypes.PortalId, portal.Id.ToString()),
new Claim(CustomClaimTypes.PortalName, portal.Name)
};
var authProperties = new AuthenticationProperties
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
IsPersistent = false
};
const string authenticationType = "Cookies";
var claimsIdentity = new ClaimsIdentity(claims, authenticationType);
await _httpContextAccessor.HttpContext.SignInAsync(authenticationType, new ClaimsPrincipal(claimsIdentity), authProperties);
}
Claims are only added on persisted in the ClaimsPrinciple/User if SignInAsync has been called again. So, your first claims will be found because that are added before the user is signed in. However, as you're adding more claims, they won't be "saved" as the user's identity has not been reset.
If you're adding more claims, prior to the initial sign in, this should work:
var authProperties = new AuthenticationProperties
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),
IsPersistent = false,
};
await httpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
_httpContextAccessor.HttpContext.User,
authProperties);

OpenId Connect and Custom Identity Framework

I'm using the Okta example for implementing OpenIdConnect in an Asp.NET 4.6.x MVC web application. The application uses Unity for Dependency Injection and one of the dependencies is a custom set of classes for the Identity Framework. I'm not using the Okta API because the IdP is not actually Okta and I'm assuming there's proprietary stuff in it. So it's all .NET standard libraries for the OpenId portions.
I can walk through the code after clicking login and it will carry me to the IdP and I can log in with my account, and then it will bring me back and I can see all of the information from them for my login. But it doesn't log me in or anything as it does in the example from Okta's GitHub.
Basically I'm wondering if the identity customization is what's interfering with the login and if there's a way to get in the middle of that and specify what I need it to do?
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions {
ClientId = clientId
, ClientSecret = clientSecret
, Authority = authority
, RedirectUri = redirectUri
, AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive
, ResponseType = OpenIdConnectResponseType.CodeIdToken
, Scope = OpenIdConnectScope.OpenIdProfile
, PostLogoutRedirectUri = postLogoutRedirectUri
, TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name" }
, Notifications = new OpenIdConnectAuthenticationNotifications {
AuthorizationCodeReceived = async n =>
{
//var tokenClient = new TokenClient($"{authority}/oauth2/v1/token", clientId, clientSecret);
var tokenClient = new TokenClient($"{authority}/connect/token", clientId, clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
//var userInfoClient = new UserInfoClient($"{authority}/oauth2/v1/userinfo");
var userInfoClient = new UserInfoClient($"{authority}/connect/userinfo");
var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
var claims = new List<System.Security.Claims.Claim>();
claims.AddRange(userInfoResponse.Claims);
claims.Add(new System.Security.Claims.Claim("id_token", tokenResponse.IdentityToken));
claims.Add(new System.Security.Claims.Claim("access_token", tokenResponse.AccessToken));
if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
{
claims.Add(new System.Security.Claims.Claim("refresh_token", tokenResponse.RefreshToken));
}
n.AuthenticationTicket.Identity.AddClaims(claims);
return;
}
, RedirectToIdentityProvider = n =>
{
// If signing out, add the id_token_hint
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenClaim != null)
{
n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
}
}
return Task.CompletedTask;
}
}
});
The token(s) returned by Okta have to be managed by your application in order to perform the login action. The OIDC token returned will need to be verified and validated by you, and then a decision made as to whether to accept the OIDC token. If so, you take action to log the user into your application. Recieving an OIDC token as a result of an OpenID Connect flow doesn't by itself log you into an app. The app needs to do some more work based on the token content before taking a login or reject action.

Jwt tokens authorization is not working

I'm trying to create Jwt token authorization. For this purpose I have issuer part with the code like that:
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] {"*"});
Users user;
using (var db = new UserStore())
{
user = Task.Run(()=> db.FindUser(context.UserName, context.Password, context.ClientId)).Result;
}
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect");
return Task.FromResult<object>(null);
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Email));
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, user.Roles.Name));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"audience", context.ClientId ?? string.Empty
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
And "resource" part that should accept bearer token:
public void ConfigureOAuth(IAppBuilder app)
{
var issuer = SiteGlobal.Issuer;
var audience = SiteGlobal.Audience;
var secret = TextEncodings.Base64Url.Decode(SiteGlobal.Secret);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audience },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, secret)
}
});
}
As far as I can see issued token are valid (I did validation on jwt.io), so the problem is somehwere else. When I'm sending token in Postman with the call to controller protected by [Authorize] attribute it always return 401 code. Could you please advise how to fix this?
P.S. This is how I implement custom Jwt fortmat:
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = data.Properties.Dictionary.ContainsKey(AudiencePropertyKey) ? data.Properties.Dictionary[AudiencePropertyKey] : null;
if (string.IsNullOrWhiteSpace(audienceId)) throw new InvalidOperationException("AuthenticationTicket.Properties does not include audience");
Audience audience;
using (var store = new AudienceStore())
{
audience = Task.Run(()=> store.FindAudience(audienceId)).Result;
}
var symmetricKeyAsBase64 = audience.Base64Secret;
var signingKey = new InMemorySymmetricSecurityKey(Encoding.UTF8.GetBytes(symmetricKeyAsBase64));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.Sha256Digest);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingCredentials);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
P.S. Guys, I'm so sorry, but I forgot to explain that "issuer" part of code that's standalone application, meanwhile "audience" is protected web api. That's two different appliactions running independently.
In Postman ensure you are sending the authorization header using the following format:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Ensure that you leave the Authorization tab set to Type: No Auth.
If you continue to have issues, set a breakpoint in your GrantResourceOwnerCredentials and see if it gets to that point. Also consider overriding the ValidateClientAuthentication method of OAuthAuthorizationServerProvider which should get called prior to GrantResourceOwnerCredentials if you want to debug earlier in the chain of events.
I have just tried to run demo project mentioned in SON Web Token in ASP.NET Web API 2 using Owin and all worked as expected.
I noticed that your implementation of Protect method differs quite a bit. I would suggest you to compare your implementation to an example given in the article. Try make that work first.
Also please make sure that issuer, audience and secret are same on both servers.
If you provide complete source code I can try to investigate more.

Custom Authorize Attribute on asp.net mvc

I have an asp.net mvc app which authorizes with Azure AAD. The app is based on this github example:
https://github.com/dushyantgill/VipSwapper/tree/master/TrainingPoint
this app has a custom authorize attribute
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext ctx)
{
if (!ctx.HttpContext.User.Identity.IsAuthenticated)
base.HandleUnauthorizedRequest(ctx);
else
{
ctx.Result = new ViewResult { ViewName = "Error", ViewBag = { message = "Unauthorized." } };
ctx.HttpContext.Response.StatusCode = 403;
}
}
}
However this seems very odd to me.
I have on a controller something like this:
public class GlobalAdminController : Controller
{
// GET: GlobalAdmin
[AuthorizeUser(Roles = "admin")]
public ActionResult Index()
{
return View();
}
}
As you can see the custom attribute is used there, but take a deeper look at the code of the custom attribute.
Apparently on both the if and ELSE the request is not authenticated.
Now take a look at this screenshot.
Makes no sense right?
http://screencast.com/t/obqXHZJj0iNG
The question, is what should I do to allow the user to execute the controller?
Update 1:
On my auth flow I have the following
public void ConfigureAuth(IAppBuilder app)
{
// configure the authentication type & settings
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
// configure the OWIN OpenId Connect options
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = SettingsHelper.ClientId,
Authority = SettingsHelper.AzureADAuthority,
Notifications = new OpenIdConnectAuthenticationNotifications()
{
// when an auth code is received...
AuthorizationCodeReceived = (context) => {
// get the OpenID Connect code passed from Azure AD on successful auth
string code = context.Code;
// create the app credentials & get reference to the user
ClientCredential creds = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
string userObjectId = context.AuthenticationTicket.Identity.FindFirst(System.IdentityModel.Claims.ClaimTypes.NameIdentifier).Value;
// use the ADAL to obtain access token & refresh token...
// save those in a persistent store...
EfAdalTokenCache sampleCache = new EfAdalTokenCache(userObjectId);
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, sampleCache);
// obtain access token for the AzureAD graph
Uri redirectUri = new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path));
AuthenticationResult authResult = authContext.AcquireTokenByAuthorizationCode(code, redirectUri, creds, SettingsHelper.AzureAdGraphResourceId);
if (GraphUtil.IsUserAADAdmin(context.AuthenticationTicket.Identity))
context.AuthenticationTicket.Identity.AddClaim(new Claim("roles", "admin"));
// successful auth
return Task.FromResult(0);
},
AuthenticationFailed = (context) => {
context.HandleResponse();
return Task.FromResult(0);
}
},
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false
}
});
}
Check specially the IsAADAdmin method call
/// <summary>
/// The global administrators and user account administrators of the directory are automatically assgined the admin role in the application.
/// This method determines whether the user is a member of the global administrator or user account administrator directory role.
/// RoleTemplateId of Global Administrator role = 62e90394-69f5-4237-9190-012177145e10
/// RoleTemplateId of User Account Administrator role = fe930be7-5e62-47db-91af-98c3a49a38b1
/// </summary>
/// <param name="objectId">The objectId of user or group that currently has access.</param>
/// <returns>String containing the display string for the user or group.</returns>
public static bool IsUserAADAdmin(ClaimsIdentity Identity)
{
string tenantId = Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string signedInUserID = Identity.FindFirst(System.IdentityModel.Claims.ClaimTypes.NameIdentifier).Value;
string userObjectID = Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
ClientCredential credential = new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's EF DB
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, new EfAdalTokenCache(signedInUserID));
AuthenticationResult result = authContext.AcquireTokenSilent(
SettingsHelper.AzureAdGraphResourceId, credential, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
HttpClient client = new HttpClient();
string doQueryUrl = string.Format("{0}/{1}/users/{2}/memberOf?api-version={3}",
SettingsHelper.AzureAdGraphResourceId, tenantId,
userObjectID, SettingsHelper.GraphAPIVersion);
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, doQueryUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
var responseContent = response.Content;
string responseString = responseContent.ReadAsStringAsync().Result;
var memberOfObjects = (System.Web.Helpers.Json.Decode(responseString)).value;
if (memberOfObjects != null)
foreach (var memberOfObject in memberOfObjects)
if (memberOfObject.objectType == "Role" && (
memberOfObject.roleTemplateId.Equals("62e90394-69f5-4237-9190-012177145e10", StringComparison.InvariantCultureIgnoreCase) ||
memberOfObject.roleTemplateId.Equals("fe930be7-5e62-47db-91af-98c3a49a38b1", StringComparison.InvariantCultureIgnoreCase)))
return true;
}
return false;
}
I am 100% sure the user is in the admin role, because when I debug it returns true and the claim is created
Update 2:
At debugging time I checked the User.Claims and the role admin is in there.
so I am not sure how this authorization per role works with User.IsInRole
http://screencast.com/t/zUbwbpzn55qb
Esteban, you seem to have missed setting the role claim type in your ConfigureAuth implementation. Please see line 55 of the sample: https://github.com/dushyantgill/VipSwapper/blob/master/TrainingPoint/App_Start/Startup.Auth.cs#L55. Once you do that User.IsInRole() and Authorize attribute will work properly.
Regd the implementation of the custom Authorize attribute - ASP.net has a bug where it returns a 401 error (instead of 403) for authorization failed (that puts the authenticated user in an endless auth loop with the IdP). This custom authorize attribute fixes that issue.
Hope that helps.
See you.
The claim type for a role is http://schemas.microsoft.com/ws/2008/06/identity/claims/role (which you can shortcut to using ClaimTypes.Role per here).
I believe your ConfigureAuth class should be changed from this:
if (GraphUtil.IsUserAADAdmin(context.AuthenticationTicket.Identity))
context.AuthenticationTicket.Identity.AddClaim(new Claim("roles", "admin"));
To this:
if (GraphUtil.IsUserAADAdmin(context.AuthenticationTicket.Identity))
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Role, "admin"));

ADAL JavaScript: Adding additional claims (ADAL JS)

I ran the ADAL JS sample SPA project from Github against my Azure AD.
That works well, but I want to add claims to the token after authentication.
In the SPA sample, you add middle-ware as follows:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});
From here, do you have to add additional OAuth middleware to get access to something like Notifications to get to the ClaimsIdentity and AddClaim?
You can use the TokenValidationParamenters. See ValidateToken or TokenValidationParameters.CreateClaimsIdentity
I found a great sample that handles exactly this... the magic happens inside Provider = new OAuthBearerAuthenticationProvider.
You see that additional claims are added to identity.
// Add bearer token authentication middleware.
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
// The id of the client application that must be registered in Azure AD.
TokenValidationParameters = new TokenValidationParameters { ValidAudience = clientId },
// Our Azure AD tenant (e.g.: contoso.onmicrosoft.com).
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
// This is where the magic happens. In this handler we can perform additional
// validations against the authenticated principal or modify the principal.
OnValidateIdentity = async context =>
{
try
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"].First();
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
// Credential representing the current user. We need this to request a token
// that allows our application access to the Azure Graph API.
var userUpnClaim = identity.FindFirst(ClaimTypes.Upn);
var userName = userUpnClaim == null
? identity.FindFirst(ClaimTypes.Email).Value
: userUpnClaim.Value;
var userAssertion = new UserAssertion(
userJwtToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
// Credential representing our client application in Azure AD.
var clientCredential = new ClientCredential(clientId, applicationKey);
// Get a token on behalf of the current user that lets Azure AD Graph API access
// our Azure AD tenant.
var authenticationResult = await authenticationContext.AcquireTokenAsync(
azureGraphApiUrl, clientCredential, userAssertion).ConfigureAwait(false);
// Create Graph API client and give it the acquired token.
var activeDirectoryClient = new ActiveDirectoryClient(
graphApiServiceRootUrl, () => Task.FromResult(authenticationResult.AccessToken));
// Get current user groups.
var pagedUserGroups =
await activeDirectoryClient.Me.MemberOf.ExecuteAsync().ConfigureAwait(false);
do
{
// Collect groups and add them as role claims to our current principal.
var directoryObjects = pagedUserGroups.CurrentPage.ToList();
foreach (var directoryObject in directoryObjects)
{
var group = directoryObject as Group;
if (group != null)
{
// Add ObjectId of group to current identity as role claim.
identity.AddClaim(new Claim(identity.RoleClaimType, group.ObjectId));
}
}
pagedUserGroups = await pagedUserGroups.GetNextPageAsync().ConfigureAwait(false);
} while (pagedUserGroups != null);
}
catch (Exception e)
{
throw;
}
}
}
});

Categories

Resources