Two claims principal in one request in ASP.NET Core - asp.net-core

We can access ClaimsPrincipal for current request from HttpContext.User. As it seems to me, the AuthenticateResult.Success() in custom handler sets HttpContext.User to the created ClaimsPrincipal. And that's the problem.
I use cookie authentication scheme to authenticate users and custom authentication scheme to authenticate clients.
There are scenarios, where client redirects user to my app and then I have to authenticate client that redirected the user and then the user which is redirected.
So after successful client authentication, I create authentication ticket and ClaimsPrincipal is cached in HttpContext.User. In next step, I authenticate user. And what's now? Currently existing client's ClaimsPrincipal is overriden by user's ClaimsPrincipal and I lose info about authenticated client.
So how can I have multiple ClaimsPrinciapl for HttpContext? Or is there another, better solution?

You can using below code when login.
HttpContext.Authentication.SignInAsync("Cookie", userPrincipal1,
new AuthenticationProperties
{
ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
IsPersistent = false,
AllowRefresh = false
});
When you want add another ClaimsPrincipal, you can use the code below.
var userPrincipal2= new ClaimsPrincipal(new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "Jason"),
new Claim(ClaimTypes.Role, "User"),
}));
httpContext.User.AddIdentity(userPrincipal2.Identity);
ClaimsPrincipal objects with multiple identities are referred to as "complex identities". You can use the HasClaim method to check if a claim is present in any of the identities.

Related

Web site Authentication against Web API

I have the following scenario with net core 3. A web site with a login page. This login page sends the user and password to a Web API that response with a JWT token if the credentials are correct.
How can I set now my web user as authenticated? how can I set the claims of the web user with the claims I recieve from the API token?
Is it neccessary to add any service on the startup of something similar?
Could you provide me with any basic sample of how to do it or any documentation?
Thank you
You can use cookie authentication :
In the Startup.ConfigureServices method, create the Authentication Middleware services with the AddAuthentication and AddCookie methods:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
});
And enable middleware in Configure :
app.UseAuthentication();
app.UseAuthorization();
And in the action which user post credential to , you can send a http request to web api with credential , web api will validate the credential and return back jwt token , your web application then decode token and sign in user like :
var stream = "[token]";
var handler = new JwtSecurityTokenHandler();
var tokenS = handler.ReadToken(stream) as JwtSecurityToken;
var claimsIdentity = new ClaimsIdentity(
tokenS.Claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
RedirectUri = "/Home/Privacy",
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
Depending on your front end solution, you need to figure out how to decode the JWT that you received to retrieve the values that you need.
Here are a couple of things, again depending on what you are using on the front end
C#
https://developer.okta.com/blog/2019/06/26/decode-jwt-in-csharp-for-authorization
NPM Package for SPA
https://www.npmjs.com/package/jwt-decode
Here is another good resource for JWT
https://jwt.io/
You can take the JWT you received to view the values that are in it

ServiceStack Authentication IsAuthenticated always false for users authenticated via facebook

Hi am trying to use OAuth authentication FacebookAuthProvider provided by servicestack
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider(AppSettings),
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings)
}));
I have implement custom AuthEvents and I have access to authorized user data
but after RedirectUrl happens in endpoint session is empty and IsAuthenticated property is false
In the same time from Controller I can see that created session successfully saved in Cache
This scenario occurred only if user doesn't login in facebook in the browser yet, but for user that already logged in facebook, authentication works fine
What am I doing wrong ?
Thanks in advance!
Updates:
Scenario for repsoducing:
ensure that you didn't loged in facebook in browser (open facebook.com and log out if need)
run web app (project which created with template mvcauth)
try to login via facebook
3.1. for first attempt you will be redirected to https://www.facebook.com/login.php? page for specify your facebook account credentials
after first attempt you will be returned to web app page /Account/Login#s=1 but User.Identity.IsAuthenticated = false in AccountController.Login and you still unauthorized.
after second attempt you will finally successfully logged in
At the same time after first attempt Session with user data will be created and saved in Cache, it looks like after redirecting from page 'https://www.facebook.com/login.php' а new session being created without auth data, but for second attempt we successfully login, maybe the core of issue is redirection from 'https://www.facebook.com/login.php'
In order to be able to use ServiceStack.Auth in MVC and vice-versa you'll need to register the NetCoreIdentityAuthProvider which provides integration between ServiceStack Authenticated Sessions and ASP.NET Core's Claims Principal, e.g:
public void Configure(IAppHost appHost)
{
var AppSettings = appHost.AppSettings;
appHost.Plugins.Add(new AuthFeature(() => new CustomUserSession(),
new IAuthProvider[] {
new NetCoreIdentityAuthProvider(AppSettings), /* Use ServiceStack Auth in MVC */
new CredentialsAuthProvider(AppSettings), /* Sign In with Username / Password */
new FacebookAuthProvider(AppSettings),
new GoogleAuthProvider(AppSettings),
new MicrosoftGraphAuthProvider(AppSettings),
}));
appHost.Plugins.Add(new RegistrationFeature()); //Enable /register Service
//override the default registration validation with your own custom implementation
appHost.RegisterAs<CustomRegistrationValidator, IValidator<Register>>();
}

What is AdalDistributedTokenCache when using OpenID Connect in ASP.NET Core 2.0?

The code shown here is my attempt to perform authentication in ASP.NET Core 2.0 against my Azure AD tenant.
The interesting part is my next set of objectives upon receiving an authentication code.
I want put the authenticated user's AD Groups into claims and have them passed along to my policy-based authorisation registrations.
To achieve this, I exchange the authorisation code for an access token.
Upon obtaining access token, I use Microsoft Graph SDK to retrieve the authenticated user's AD Groups.
Question 1: I have seen examples where the access token is stored in a cache IDistributedCache. Why is this important and what risk is there in not performing this step and what exactly is AdalDistributedTokenCache?
e.g.
var cache = new AdalDistributedTokenCache(distributedCache, userId);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
I find the access token is always at hand via
string accessToken = await HttpContext.GetTokenAsync("access_token");
Question 2: After retrieving groups, if I add these as claims to the Principal, can I then use them to drive authorization policies as described here?
Policy-based authorisation in ASP.NET Core
Question 3: Does the access token and id token along with the claims I add end up inside the cookie?
Question 4: How can I force Azure AD to return AD Roles as claims (not groups as I can get these via Graph) without having to change some kind of manifest?
Full code
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
Configuration.GetSection("OpenIdConnect").Bind(options);
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
// Exchange authorization code for access token
var request = ctx.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
var authContext = new AuthenticationContext(ctx.Options.Authority);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, ctx.Options.Resource);
// Use Microsoft Graph SDK to retrieve AD Groups
var email = ctx.Principal.Claims.First(f => f.Type == ClaimTypes.Upn).Value;
GraphServiceClient client = new GraphServiceClient(
new DelegateAuthenticationProvider(
async requestMessage => {
var accessToken = result.AccessToken;
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
}));
var groups = await client.Users[email].GetMemberGroups(false).Request()
.PostAsync();
// Do something with groups
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Index");
});
}
Question 1: I have seen examples where the access token is stored in a cache IDistributedCache. Why is this important and what risk is there in not performing this step and what exactly is AdalDistributedTokenCache?
ADAL uses an in-memory token cache by default where it keeps the access and refresh tokens it acquires.
By using a distributed cache backed by e.g. Redis, all of the instances hosting the app can access the token cache.
This is required if the app runs behind a load balancer, and also prevents the data from being lost when the app restarts.
Question 2: After retrieving groups, if I add these as claims to the Principal, can I then use them to drive authorization policies as described here?
You can add a new identity on the user principal, similar to my article: https://joonasw.net/view/adding-custom-claims-aspnet-core-2.
It should work if you add the identity in the OnAuthorizationCodeReceived handler.
They will be stored as claims using the default sign-in scheme, which is Cookies in your case.
So yes, you can use them in policies then.
Question 3: Does the access token and id token along with the claims I add end up inside the cookie?
Yes, they are all persisted in the cookie.
However, you should use ADAL to get the access token when you need it.
The option to save tokens is not really needed in your case, as long as you set up the ADAL token cache correctly.
Acquiring the token: https://github.com/juunas11/aspnetcore2aadauth/blob/master/Core2AadAuth/Startup.cs#L75
Using a token: https://github.com/juunas11/aspnetcore2aadauth/blob/master/Core2AadAuth/Controllers/HomeController.cs#L89
The sample app first creates a token cache for the signed-in user.
Then, we use ADAL's AcquireTokenSilentAsync method to get an access token silently.
This means ADAL will return the cached access token, or if it has expired, uses the cached refresh token to get a new access token.
If both of those fail, an exception is thrown.
In the case of the sample app, there is an exception filter that catches the exception and redirects the user to login: https://github.com/juunas11/aspnetcore2aadauth/blob/master/Core2AadAuth/Filters/AdalTokenAcquisitionExceptionFilter.cs
Question 4: How can I force Azure AD to return AD Roles as claims (not groups as I can get these via Graph) without having to change some kind of manifest?
If you mean roles like Global Administrator, you cannot get that in claims.
Roles which you define in the app manifest, and assign to users/groups are always included in the token. https://joonasw.net/view/defining-permissions-and-roles-in-aad

Trouble getting ClaimsPrincipal populated when using EasyAuth to authenticate against AAD on Azure App Service in a Asp.Net Core web app

We have a web app built on Asp.Net core. It doesn't contain any authentication middleware configured in it.
We are hosting on Azure App Service and using the Authentication/Authorization option (EasyAuth) to authenticate against Azure AD.
The authentication works well - we get the requisite headers inserted and we can see the authenticated identity at /.auth/me. But the HttpContext.User property doesn't get populated.
Is this a compatibility issue for Asp.Net core? Or am I doing something wrong?
I've created a custom middleware that populates the User property until this gets solved by the Azure Team.
It reads the headers from the App Service Authentication and create a a user that will be recognized by the [Authorize] and has a claim on name.
// Azure app service will send the x-ms-client-principal-id when authenticated
app.Use(async (context, next) =>
{
// Create a user on current thread from provided header
if (context.Request.Headers.ContainsKey("X-MS-CLIENT-PRINCIPAL-ID"))
{
// Read headers from Azure
var azureAppServicePrincipalIdHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"][0];
var azureAppServicePrincipalNameHeader = context.Request.Headers["X-MS-CLIENT-PRINCIPAL-NAME"][0];
// Create claims id
var claims = new Claim[] {
new System.Security.Claims.Claim("http://schemas.microsoft.com/identity/claims/objectidentifier", azureAppServicePrincipalIdHeader),
new System.Security.Claims.Claim("name", azureAppServicePrincipalNameHeader)
};
// Set user in current context as claims principal
var identity = new GenericIdentity(azureAppServicePrincipalIdHeader);
identity.AddClaims(claims);
// Set current thread user to identity
context.User = new GenericPrincipal(identity, null);
};
await next.Invoke();
});
Yes, this is a compatibility issue. ASP.NET Core does not support flowing identity info from an IIS module (like Easy Auth) to the app code, unfortunately. This means HttpContext.User and similar code won't work like it does with regular ASP.NET.
The workaround for now is to invoke your web app's /.auth/me endpoint from your server code to get the user claims. You can then cache this data as appropriate using the x-ms-client-principal-id request header value as the cache key. The /.auth/me call will need to be properly authenticated in the same way that calls to your web app need to be authenticated (auth cookie or request header token).
I wrote a small basic middleware to do this. It will create an identity based off of the .auth/me endpoint. The identity is created in the authentication pipeline so that [authorize] attributes and policies work with the identity.
You can find it here:
https://github.com/lpunderscore/azureappservice-authentication-middleware
or on nuget:
https://www.nuget.org/packages/AzureAppserviceAuthenticationMiddleware/
Once added, just add this line to your startup:
app.UseAzureAppServiceAuthentication();
The following code decrypts the AAD token from the Azure App Service HTTP header and populates HttpContext.User with the claims. It's rough as you'd want to cache the configuration rather than look it up on every request:
OpenIdConnectConfigurationRetriever r = new OpenIdConnectConfigurationRetriever();
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.Endpoint, r);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys.ToList(),
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = true,
ValidAudience = options.Audience,
ValidateLifetime = true,
ClockSkew = new TimeSpan(0, 0, 10)
};
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
string token = context.Request.Headers["X-MS-TOKEN-AAD-ID-TOKEN"];
if (!String.IsNullOrWhiteSpace(token))
{
principal = handler.ValidateToken(token, tokenValidationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null) { throw new ArgumentException("Invalid JWT"); }
if (principal != null)
{
context.User.AddIdentities(principal.Identities);
}
}
It only works for Azure AD. To support other ID providers (Facebook, Twitter, etc) you'd have to detect the relevant headers and figure out how to parse each provider's token. However, it should just be variations on the above theme.
You can give this library a try. I faced a similar problem and created this to simplify the use.
https://github.com/dasiths/NEasyAuthMiddleware
Azure App Service Authentication (EasyAuth) middleware for ASP.NET
CORE with fully customizable components with support for local
debugging
It hydrates the HttpContext.User by registering a custom authentication handler. To make things easier when running locally, it even has the ability to use a json file to load mocked claims.

How to do multiple-step login in IdentityServer4?

We were using IdentityServer3, implicit grant and the login consists of multiple screen. In IdentityServer3, there's built in support for such multiple step login workflow (for example for accepting EULA, two-factor login, etc.), The feature called "partial login" and there is even an example: https://github.com/IdentityServer/IdentityServer3.Samples/tree/master/source/CustomUserService/CustomUserService
We've recently upgraded to AspNetCore and IdentityServer4 and wondering how suppose to achieve the same? That is, check username and password in the first step, and if correct, store it securely (for example in an encrypted cookie) for the next step(s).
Our solution was to replicate the IdentityServer3's partial login: use a custom cookie to persist data between steps.
First, we need to register our custom cookie authentication (at Startup.Configure)
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "my-partial",
AutomaticAuthenticate = false,
AutomaticChallenge = false
});
The first step/entry point of the login workflow should be mapped to GET /account/login (as of IdentityServer4 1.0.0-rc2).
In second step, after the credentials are sent and verified, we persist the username (and eventually any other data) into a cookie.
Code:
var claims = new []
{
new Claim("my-user", username),
new Claim("some-attribute", someAttribute)
};
await HttpContext.Authentication
.SignInAsync("my-partial", new ClaimsPrincipal(new ClaimsIdentity(claims)));
Important: avoid using POST /account/login as a second step. Because regardless of your result, IdentityServer's middleware will redirect you back to the authorization endpoint (as of RC2). Just pick any other path.
At your last step, key parts
we read the persisted data from the cookie
remove the partial cookie
sign in the "real" user
redirect to returnUrl (this was added to the first step as a query parameter. Don't forget to send along it)
In code
var partialUser = await HttpContext.Authentication.AuthenticateAsync("my-partial");
var username = partialUser?.Claims.FirstOrDefault(c => c.Type == "dr-user")?.Value;
var claims = new [] { /* Your custom claims */};
await HttpContext.Authentication
.SignOutAsync("my-partial");
await HttpContext.Authentication
.SignInAsync(username, username, claims);
return Redirect(returnUrl);
In addition, you might want to validate inputs, for example return to the first step, if there is no partial cookie, etc.