How to receive the current user with create react app -auth template on net core 3 and identity server - authorization

I have never used identity server before and I was wondering how to receive the current user info if they have been authenticated using net core 3.
Using the barebones create react app -auth weather app template
in the WeatherForecastController we have the authorize attribute:
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
With the following endpoint:
[HttpGet]
public IEnumerable<WeatherForecast> Get()
Which will return the correct data if you have logged in and been granted a jwt bearer token.
I would like to create an endpoint that:
1. Allows anonymous access(logged in or not so the authorize attribute needs ditching or overriding)
2. It should then try to authenticate the user
3.If the user can be identified then the endpoint will return the correct data else if the user cannot be identified it will return a subset of the data
I'm struggling to retrieve the user who has made the request information, I believe i'm supposed to use GET /connect/userinfo identityserver endpoint with the bearer token as a parameter but I have no idea how to do this. I would like to access the user_id so I can corrispond with the identityUser table.
I have also tried:
var identity = (ClaimsIdentity)User.Identity;
IEnumerable<Claim> claims = identity.Claims;
It just feels like theres a proper way to do this stuff that i'm missing. I'd be grateful for someone to point me in the right direction.

It should then try to authenticate the user
Browsers communicate with web applications. You should have a web application to be able to redirect back to IDS4 to login. What you are have is an API. Later Web applications communicate with web APIs. Read more here
If the user can be identified then the endpoint will return the correct data else if the user cannot be identified it will return a subset of the data
What you are looking for is Authorization, you can manually do this check on the endpoint. You can access current user using ControllerBase.User ass a result of passing a valid JWT tokens to the API. Considering you already setup authentication using code like bellow:
services.AddAuthentication("Bearer").AddJwtBearer("Bearer",
options =>
{
options.Authority = "http://localhost:5000";
options.Audience = "api1";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"//To set Identity.Name
};
});
I'm struggling to retrieve the user who has made the request information
You can simply use ControllerBase.User to get the current authenticated user on API, no need to call any endpoint.
Update: Setting NameClaimType as posted on code above will set Identity.Name to the user's identifier.

Related

API Authentication using multiple Authentication Providers

This topic is one that feels like it should be documented better - or perhaps I am using the wrong terms when searching.
I have several SPA apps that use various Oauth2 logins
(ie. Okta, Facebook, Google) to authenticate and generate access
tokens.
These apps all access a common API backend (asp.net core). All
requests to the API have the Oauth2 access token attached as an Authorization header.
How do I configure this single backend API to validate these access tokens from one of a variety of providers, without knowing in advance which access token is attached, and decode a user email address that I can use for further authorization purposes?
I have found much documentation on validating tokens from a descrete, known authorization provider, but very little on using multiple providers. With all the apps out there that give you a choice of Oauth2 logons to choose from (StackOverflow among them), I thought this would be a more common problem.
What am I missing!?
It seems like the correct way to address this situation is to build a Custom Authentication Handler as documented here: https://referbruv.com/blog/posts/implementing-custom-authentication-scheme-and-handler-in-aspnet-core-3x
In this Authentication Handler I can decode the token, assert that the issuer is a member of a whitelist, validate the access token using the issuer's public key, and use the rest of the token to build the Identity I need for further authorization.
At least now I have a better idea what to search for, and I'm not completely re-inventing the authentication mechanism!
You will want to identify the user in a consistent way in your APIs, then authorize requests based on the identity + scopes.
This will be very difficult when using many different token providers, as you are finding. Their access tokens are not designed for you to use in your own APIs.
A better mechanism is to use tokens only from your own Authorization Server, to support different login methods but also put your code in control. My Federated Logins blog post has further info.
It turns out I was overthinking this after all.
Since I am dealing with an API backend, all I needed to do was to validate IDP Bearer tokens, not to create them. In the end, I was able to validate 3 ID providers using the folowing simple code:
services.AddAuthentication(OKTA_SCHEME)
.AddJwtBearer(ADFS_SCHEME, options =>
{
options.Authority = adfsConfig.authority;
options.Authority = adfsConfig.authority;
})
.AddJwtBearer(GOOGLE_SCHEME, jwt => jwt.UseGoogle(
clientId: googleConfig.clientId
))
.AddJwtBearer(OKTA_SCHEME, options =>
{
options.Authority = oktaConfig.authority;
options.Audience = oktaConfig.audience;
});
Note that this required the installation of one additional nuget package to simplify the validation of the Google tokens, which don't appear to follow the standard: Hellang.Authentication.JwtBearer.Google.
At this point I can authorize using attributes like:
[Authorize(AuthorizationSchemes = OKTA_SCHEME)]
...or set up policies based on the schemes.
The second part problem was to link my various logons to users in a local database, which I ended up doing using a custom IClaimsTransformation that uses the information populated to ClaimsPrincipal to lookup a the user in my database, and add an "Employee" role claim, if they are found.
public class EmployeeClaims : IClaimsTransformation
{
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
if (!principal.HasClaim(a => a.Type == "EmployeeNumber"))
{
Employee employee = lookupEmployee(principal);
if (employee != null)
{
ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim(ClaimTypes.Role, "Employee"));
id.AddClaim(new Claim("EmployeeNumber", employee.EmployeeNumber.ToString()));
principal.AddIdentity(id);
}
}
return Task.FromResult(principal);
}
private Employee lookupEmployee(ClaimsPrincipal principal) {
string issuer = principal.Claims.Single(a => a.Type == "iss").Value;
if (issuer.Contains("google.com"))
...
}
}
This IClaimsTransformation is then registered by:
services.AddScoped<IClaimsTransformation, EmployeeClaims>();
Now I can additionally authorize employees with:
[Authorize(Roles = "Employee")]

Can I Combine Identities from two Authentication Schemes in ASP.NET Core 3?

I have a web application that utilizes our organization's CAS servers for authentication. I am currently developing an integration with DocuSign for this application. A user will first come to our site and sign in with CAS. Then, they can go to the DocuSign area of the application, sign in to DocuSign, and then perform some functions tied to the DocuSign API. Each of these pieces works properly within my application.
My current problem is that I cannot access both Identities within the HttpContext simultaneously. I need the CAS Identity to determine user behavior and access to certain functions. I need the DocuSign Identity to get the necessary values to enable the API calls. In Startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/Login");
})
.AddCAS(options =>
{
options.CasServerUrlBase = Configuration["CasBaseUrl"];
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOAuth("DocuSign", options ->
{
//necessary DocuSign options
});
I have a HomeController that uses the Authorize attribute and properly requires CAS to access. I can properly access the claims from the CAS Identity. I have a DocusignController where I use Authorize(AuthenticationSchemes = "DocuSign"). An ActionFilter that applies to this controller shows that the DocuSign Identity is coming through properly. Unfortunately, the CAS Identity is not attached, so I cannot check for things such as admin permissions. I examined the cookies, and saw that the .AspNetCore.Cookies value changes when I go between the different Authentication Schemes.
One attempt at a solution was to change the DocusignController to have Authorize(AuthenticationSchemes = "DocuSign, CAS" as its attribute. This seems to be an 'or' rather than an 'and' of the two schemes. If CAS was the most recent scheme, then the CAS Identity is the one seen in my ActionFilter. Same for DocuSign.
Another attempt was to create my own AuthenticationHandler to combine the two Identities. Appended to the AddAuthentication above:
.AddScheme<CombinedSchemeOptions, CombinedHandler>
("Combined", op => { });
Then, within this CombinedHandler:
protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var CasResult = await Context.AuthenticateAsync("CAS");
if(CasResult?.Principal != null)
{
principal.AddIdentities(CasResult.Principal.Identities);
}
var DSResult = await Context.AuthenticateAsync("DocuSign");
if(DSResult?.Principal != null)
{
principal.AddIdentities(DSResult.Principal.Identities);
}
var ticket = new AuthenticationTicket(principal, "Combined");
return AuthenticateResult.Success(ticket);
}
This seems to behave the same way as before. The most recent Scheme I've used, upon entering the Combined scheme, will give a successful Result with an Identity, and the other Result will return with a failed result.
Is it possible for me to have these two Identities within the HttpContext.User simultaneously? Is it otherwise possible for me to combine claims from the two Identities into one and pass that along?
Further research and tinkering has led me to a solution. First, I discovered that I needed the two authentication schemes to write to separate cookies. I then made sure the DocuSign scheme used the new cookie as its SignInScheme.
.AddCookie("DSCookie")
.AddOAuth("DocuSign", options =>
{
options.SignInScheme = "DSCookie";
//further DocuSign options
}
I also learned that the Authorize tag works as an OR statement (if you have CAS OR DocuSign, you are authorized for this action), yet it will pass along all valid identities permitted by the scheme (You have CAS and you have DocuSign? Bring both Identities in with you). Thus, the second part of my solution has a single action that requires DocuSign authentication, prompting the necessary challenge. Then, I move to actions that have both authentication schemes, and both Identities are brought along properly.
EDIT: To elaborate per Dmitriy's request
[Authorize(AuthenticationSchemes = "DocuSign")]
public ActionResult Index()
{
return RedirectToAction("Control");
}
[Authorize(AuthenticationSchemes = "DocuSign,CAS")]
public ActionResult Control()
{
//relevant code
}
I click on a link to go to the Index action. Since I currently do not have a valid DocuSign Identity, it performs the necessary prompt and takes me to the sign in screen. If I linked directly to Control, Authentication would see that I currently had a valid CAS Identity, so would approve my access, even though I did not yet have the DocuSign Identity.
After the sign in, when I arrive at the Index function, only the DocuSign Identity is currently available. I then redirect to Control. That function verifies I have at least one of the specified Identities, grants me access, and passes along any of the listed Identities I currently possess. Thus, inside Control, I can grab information from both CAS and DocuSign.

What is the purpose of additionalLocalClaims in Identity Server 4 SignInAsync

In Identity Server 4, Quick Start, ExternalController.cs - CallBack method, I found the following:
// this allows us to collect any additonal claims or properties
// for the specific prtotocols used and store them in the local auth cookie.
// this is typically used to store data needed for signout from those protocols.
var additionalLocalClaims = new List<Claim>();
var localSignInProps = new AuthenticationProperties();
ProcessLoginCallbackForOidc(result, additionalLocalClaims, localSignInProps);
// issue authentication cookie for user
await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, localSignInProps, additionalLocalClaims.ToArray());
You can see the complete set of code at this link - IdentityServer4.Quickstart.UI
I tried to add some claims to that additionalLocalClaims list by doing the following:
additionalLocalClaims.Add(new Claim("TestName", "TestValue"));
But it never appears in the UserClaims or AccessToken even though ClaimType "TestName" is included in ApiResource.
I would like to add some custom claims/values in AccessToken for Google Authentication and I thought additionalLocalClaims is the right one to append additional claims.
P.S. I finally implemented IProfileService and could return the additional claims. But I still want to know what's the use case of that additionalLocalClaims in HttpContext.SignInAsync extension method.
There are authentication-related extension methods on the HttpContext from ASP.NET Core to issue the authentication cookie and sign a user in , and you can add custom cliams to cookie for authentication session of Identity Server 4 .For example , you can add claims which needed for signout , some external idenity provider may issue a session id claim , you can involve it in local auth cookie via additionalLocalClaims , which could be used to perform single sign-out from external identity provider if user logout in your identity server . But the claims are kept in local auth cookie of IDS4 , they are not in ID token ,access token and userinfo endpoint . You can implement IProfileService as you said to involve custom claims in tokens or userinfo endpoint .

IdentityServer4 as Web API

I'm newbie in IdentityServer4. Now I try to realize microservices with JWT-authorization (by passing tokens each request to the API). So, the schema is primitive - IDS4 server as an auth server, ASP.NET Identity + MSSQL for client data usage and storage, other services use auth service for token validation.
So, I've looked many articles, but found none of example, where I can customize IDS4 behavior.
For example, I want client to call AuthorizeByLogin(AuthorizeView model) API method, realized in the IDS4 project, where model is an object of 2 fields: Username, Password. In this method I want to check user in the Database and generate access_token, which is passed to the client for working with protected API.
But there is nowhere an example how to do this (call API method, pass the object and receive token). Most of it says "use */connect/token for this".
Could anyone give me an example of code in which this way is realized well? Or maybe tell me which interfaces I should implement and correctly pass to the services in ASP.NET Core app for Authentication Web API + IdentityServer4 realization?
Thank you.
You can find a plethora of quickstart examples in the Identity Server 4 docs. Also from what I understand, you are wanting to use ResourceOwnerCredentials grant type. You won't be able to easily modify the endpoints that issue the tokens without reimplementing majority of Identity Server 4 but you can implement IResourceOwnerPasswordValidator interface and setup a client with an appropriate allowed grant type:
new Client
{
ClientId = "your_client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("your_password".Sha256())
},
AllowedScopes = { ... }
}
After that, the clients can call connect\token endpoint by providing user name and password of a given user alongside their own credentials:
var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "ro.client",
ClientSecret = "secret",
UserName = "alice",
Password = "password",
Scope = "api1"
});

OpenID Connect server. Generate access token based on 3-rd party token (social login)

I had implemented OpenID Connect server that generates access tokens for mobile client based on username/password using OpenIddict.
My next goal was to provide ability to generate Access Token using 3-rd party tokens (social login for example), and I started from integration with Google token, but stuck as cannot find any samples/informations about how to do this.
The only one idea that I currently have is to make request to "/connect/token" endpoint and send Google token in "code" parameter, for example in "google:" format, then override OpenIdConnectServerProvider.DeserializeAuthorizationCode method:
Called when receiving an authorization code. An application may use this context to deserialize the code using a custom format and to skip the default logic using
So I have created own CustomProvider class based on OpenIddictProvider, registered it
services.AddOpenIddict<ApplicationUser, ApplicationRole, ApplicationDbContext, int>()
.Configure(builder =>
{ builder.Provider = new CustomProvider(sp.GetRequiredService<SignInService>()); }
and overridden the DeserializeAuthorizationCode method:
public override async Task DeserializeAuthorizationCode(DeserializeAuthorizationCodeContext context)
{
string code = context.Request.Code;
if (code.StartsWith("google:"))
{
string token = code.Replace("google:", "");
var principal = new GoogleTokenValidator().ValidateToken(token, null).Result;
var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), "Bearer");
ticket.SetPresenters(context.Request.ClientId);
context.Ticket = ticket;
context.Ticket.Properties.ExpiresUtc = DateTime.UtcNow.AddDays(1);
context.HandleResponse();
await _signInService.Login(principal);
return;
}
else
{
base.DeserializeAuthorizationCode(context);
}
}
where GoogleTokenValidator is a custom class for Google token handling (it makes call to Google User Information Endpoint and generate ClaimsPrincipal), based on "copy-pasted" code from GoogleHandler class in aspnet/Security repo.
In general it is working with some additional hacks, but I have strong feeling that reinventing the wheel...
In general it is working with some additional hacks, but I have strong feeling that reinventing the wheel...
You're not only reinventing the wheel, but you're also implementing something totally non-standard that is not supported (at all) by OpenIddict.
Here's the approach I recommend (which is the one we use in the MVC server sample):
The OAuth2/OpenID Connect client application redirects the user agent to your authorization controller (you can take a look at this controller for an example).
OpenIddict will validate the authorization request and allow your controller to be invoked if it's fully valid.
If the user is not already logged in, your authorization controller will redirect the user to the login endpoint, provided by AccountController. At this point, you're free to propose local authentication (e.g using a username/password couple) or Google authentication (you can use the Google authentication middleware for that). You can even offer 2-FA as part of this login process.
Once the user is logged in (e.g after a registration process and/or an external authentication association), his/her browser is redirected back to the authorization endpoint, where a consent form indicating he/she's about to allow your JS app to access his personal data on his/her behalf is displayed.
When the user allows your client application to access his data, the request is handled by your authorization controller, that calls SignInAsync to inform OpenIddict that an authorization code/access token should be returned to your application.