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;
}
}
}
});
Related
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.
I am using ASP.NET Core 2.0, with Azure AD v2.0 endpoint.
I am getting claims like this:
var currentUser = User;
var displayName = currentUser.FindFirst("name").Value;
var claims = currentUser.Claims;
I am not used to using this User to get claims, but could not get the old way with System.Security.Claims to work. So my first question is, is this how I should be getting my claims? And my second question is, how do I add claims to this User?
is this how I should be getting my claims?
AFAIK, you could leverage ControllerBase.HttpContext.User or ControllerBase.User for retrieving the System.Security.Claims.ClaimsPrincipal for current user. Details you could follow the similar issue1 and issue2.
And my second question is, how do I add claims to this User?
As you said you are using ASP.NET Core 2.0, with Azure AD v2.0. I assumed that when using UseOpenIdConnectAuthentication, you could add the additional claims under OnTokenValidated as follows:
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["AzureAD:ClientId"],
Authority = string.Format(CultureInfo.InvariantCulture, Configuration["AzureAd:AadInstance"], "common", "/v2.0"),
ResponseType = OpenIdConnectResponseType.IdToken,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
Events = new OpenIdConnectEvents
{
OnRemoteFailure = RemoteFailure,
OnTokenValidated = TokenValidated
},
TokenValidationParameters = new TokenValidationParameters
{
// Instead of using the default validation (validating against
// a single issuer value, as we do in line of business apps),
// we inject our own multitenant validation logic
ValidateIssuer = false,
NameClaimType = "name"
}
});
private Task TokenValidated(TokenValidatedContext context)
{
/* ---------------------
// Replace this with your logic to validate the issuer/tenant
---------------------
// Retriever caller data from the incoming principal
string issuer = context.SecurityToken.Issuer;
string subject = context.SecurityToken.Subject;
string tenantID = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
// Build a dictionary of approved tenants
IEnumerable<string> approvedTenantIds = new List<string>
{
"<Your tenantID>",
"9188040d-6c67-4c5b-b112-36a304b66dad" // MSA Tenant
};
o
if (!approvedTenantIds.Contains(tenantID))
throw new SecurityTokenValidationException();
--------------------- */
var claimsIdentity=(ClaimsIdentity)context.Ticket.Principal.Identity;
//add your custom claims here
claimsIdentity.AddClaim(new Claim("test", "helloworld!!!"));
return Task.FromResult(0);
}
Then, I used the following code to retrieve the user claims:
public IActionResult UserInfo()
{
return Json(User.Claims.Select(c=>new {type=c.Type,value=c.Value}).ToList());
}
Test:
Moreover, you could refer to this sample Integrating Azure AD (v2.0 endpoint) into an ASP.NET Core web app.
This question already has answers here:
Authorize By Group in Azure Active Directory B2C
(8 answers)
Closed last year.
I have an Asp.NET MVC Application connected with Azure AD B2C.
In the Administrator settings I've created an Administrators Group:
In my code I would like to use [Authorize(Roles = "Administrator")]
With regular Azure Active Directory it was easy to add (just 3 lines of code). But for the Azure AD B2C I cannot find any tutorial or example in the web which is working. Maybe you can tell me what i need to modify.
Here is the ConfigureAuth method of my Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (separated by a blank space)
Scope = $"openid profile offline_access {ReadTasksScope} {WriteTasksScope}"
}
);
}
Azure AD B2C does not yet include Group claims in the token it sends to the application thus you can't follow the same approach as you outlined with Azure AD (which does include group claims in the token).
You can support this feature ask by voting for it in the Azure AD B2C feedback forum: Get user membership groups in the claims with Azure AD B2C
That being said, you can do some extra work in this application to have it manually retrieve these claims the group claims and inject them into the token.
First, register a separate application that'll call the Microsoft Graph to retrieve the group claims.
Go to https://apps.dev.microsoft.com
Create an app with Application Permissions : Directory.Read.All.
Add an application secret by clicking on Generate new password
Add a Platform and select Web and give it any redirect URI, (e.g. https://yourtenant.onmicrosoft.com/groups)
Consent to this application by navigating to: https://login.microsoftonline.com/YOUR_TENANT.onmicrosoft.com/adminconsent?client_id=YOUR_CLIENT_ID&state=12345&redirect_uri=YOUR_REDIRECT_URI
Then, you'll need to add code the following code inside of the OnAuthorizationCodeReceived handler, right after redeeming the code:
var authority = $"https://login.microsoftonline.com/{Tenant}";
var graphCca = new ConfidentialClientApplication(GraphClientId, authority, GraphRedirectUri, new ClientCredential(GraphClientSecret), userTokenCache, null);
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
try
{
AuthenticationResult authenticationResult = await graphCca.AcquireTokenForClientAsync(scopes);
string token = authenticationResult.AccessToken;
using (var client = new HttpClient())
{
string requestUrl = $"https://graph.microsoft.com/v1.0/users/{signedInUserID}/memberOf?$select=displayName";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
foreach (var group in json["value"])
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, group["displayName"].ToString(), System.Security.Claims.ClaimValueTypes.String, "Graph"));
//TODO: Handle paging.
// https://developer.microsoft.com/en-us/graph/docs/concepts/paging
// If the user is a member of more than 100 groups,
// you'll need to retrieve the next page of results.
}
} catch (Exception ex)
{
//TODO: Handle
throw;
}
Is there a way to take a Bearer Token string and convert it to the Identity object manually in asp.net?
Cheers,
Aziz
This is a pretty old question, but I think answer was still missing. I was able to regenerate Principal by using the following line
var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(accessToken);
var identity = ticket.Identity;
First you need to crate some claims based on token then create ClaimsIdentity and use it to authorize the user.
public ActionResoult Login(string token)
{
if(_tokenManager.IsValid(token))
{
// optionally you have own user manager which returns roles and user name from token
// no matter how you store users and roles
var user=_myUserManager.GetUserRoles(token);
// user is valid, going to authenticate user for my App
var ident = new ClaimsIdentity(
new[]
{
// adding following 2 claim just for supporting default antiforgery provider
new Claim(ClaimTypes.NameIdentifier, token),
new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
// an optional claim you could omit this
new Claim(ClaimTypes.Name, user.Username),
// populate assigned user's role form your DB
// and add each one as a claim
new Claim(ClaimTypes.Role, user.Roles[0]),
new Claim(ClaimTypes.Role, user.Roles[1]),
// and so on
},
DefaultAuthenticationTypes.ApplicationCookie);
// Identity is sign in user based on claim don't matter
// how you generated it
HttpContext.GetOwinContext().Authentication.SignIn(
new AuthenticationProperties { IsPersistent = false }, ident);
// auth is succeed, just from a token
return RedirectToAction("MyAction");
}
// invalid user
ModelState.AddModelError("", "We could not authorize you :(");
return View();
}
Now you could use Authorize filter as well:
[Authorize]
public ActionResult Foo()
{
}
// since we injected user roles to Identity we could do this as well
[Authorize(Roles="admin")]
public ActionResult Foo()
{
// since we injected our authentication mechanism to Identity pipeline
// we have access current user principal by calling also
// HttpContext.User
}
Also I encourage you to have look Token Based Authentication Sample from my github repo as a very simple working example.
The token just holds claims and it's just used for authentication into the resource. If one of those claims held user information you could create an identity and assign the claims to it.
public void ValidateBearerToken(OwinContext context)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
byte[] securityKey = GetBytes("some key"); //this should come from a config file
SecurityToken securityToken;
var validationParameters = new TokenValidationParameters()
{
ValidAudience = "http://localhost:2000",
IssuerSigningToken = new BinarySecretSecurityToken(securityKey),
ValidIssuer = "Self"
};
var auth = context.Request.Headers["Authorization"];
if (!string.IsNullOrWhiteSpace(auth) && auth.Contains("Bearer"))
{
var token = auth.Split(' ')[1];
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
context.Request.User = principal;
}
}
catch (Exception ex)
{
var message = ex.Message;
}
}
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);
}