Visual Studio provides a nice ready template for ASP.NET WEB API project. There we have a set of account management functions dealing with ASP.NET Identity. However, one most fundamental function is missing both from automatically generated controllers and documentation. Namely, "~/Token" URL, which is used to grant WEB API access tokens is not mentioned anywhere.
I would like to write a custom controller to intercept all "~/Token" calls to make some logging and additional processing in a way similar to other WEB API controllers. How can I do it in a simple and natural way?
It seems you need OWIN OAuth 2.0 Authorization Server. This is the Microsoft extension to add the required functionality. It creates an oauth endpoint (e.g. /token) that you can use to get a token. You don't have a controller directly, but there is a special OWIN class connected to it that you will need to extend to add whatever you need.
You can find more details here and here.
It's a bit long reading, but it works and I have used it in a few projects.
Here is a simple example how you can do it (GrantResourceOwnerCredentials is the most important method for you):
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Add CORS e.g.
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (AuthRepository _repo = new AuthRepository())
{
IdentityUser user = await _repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim("sub", context.UserName));
identity.AddClaim(new Claim("role", "user"));
context.Validated(identity);
}
}
Related
With reference to this article Authorization Request.
If my application user is already logged in, how can I request to get my profile using SDK or this API reference: https://graph.microsoft.com/v1.0/me/
SDK code I wrote:
public async Task<string> GetFullNameAsync()
{
try
{
var user = await _client.Me
.Request()
.Select(data => data.DisplayName)
.GetAsync();
return user.DisplayName;
}
catch (Exception ex)
{
return "404 Not Found";
}
}
For that, how can I get a bearer token string using delegated authentication for Web Application and single tenant in .NET Core as mentioned in the above article?
To get bearer token using delegated authentication, please check the below if helpful:
Make use of Microsoft.Identity.Web.MicrosoftGraph nuget package to implement Graph API from an ASP. NET Core web application.
The application authentication and the authorization are setup in the Startup class.
For delegated user access token make use of AddMicrosoftIdentityWebApiAuthentication method as below sample code:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddScoped<GraphApiClientDirect>();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
Use GetAccessTokenForUserAsync method, to get new delegated access token for required scopes.
The Graph API client service can then be used in the API which is protected using the Microsoft.Identity .Web packages.
[Authorize]
[ApiController]
[Route("[controller]")]
public class GraphCallsController : ControllerBase
{
private readonly GraphApiClientDirect _graphApiClientDirect;
public GraphCallsController(GraphApiClientDirect graphApiClientDirect)
{
_graphApiClientDirect = graphApiClientDirect;
}
[HttpGet]
public async Task<string> Get()
{
var user = await _graphApiClientDirect.GetGraphApiUser()
.ConfigureAwait(false);
return user.DisplayName;
}
}
The correct initialization must be used while using Graph API client in ASP.NET Core applications for delegated access user access tokens.
Use the IHttpClientFactory to create the HttpClient instance to create instance.
Use ITokenAcquisition and the IHttpClientFactory interfaces to create the GraphApiClient requests for different scopes.
Please find below references to know more in detail:
Using Microsoft Graph API in ASP.NET Core | Software Engineering (damienbod.com)
Secure a .NET Core API using Bearer Authentication - Dotnet Playbook
Background
I've been following the documentation for using IdentityServer4 with single-page-applications on ASP.NET-Core 3.1 and as such created a project via the dotnet new react -au Individual command.
This creates a project which uses the Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet package.
So far it's been really great and it got token-based authentication for my ReactJS application working without any pain!
From my ReactJS application, I can access the user information populated by the oidc-client npm package such as the username.
Also, calls to my Web APIs with the [Authorize] attribute work as expected: only calls with a valid JWT access token in the request header have access to the API.
Problem
I'm now trying to access basic user information (specifically username) from within a GraphQL mutation resolver via an injected IHttpContextAccessor but the only user information I can find are the following claims under IHttpContextAccessor.HttpContext.User:
nbf: 1600012246
exp: 1600015846
iss: https://localhost:44348
aud: MySite.HostAPI
client_id: MySite
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: (actual user GUID here)
auth_time: 1600012235
http://schemas.microsoft.com/identity/claims/identityprovider: local
scope: openid
scope: profile
scope: MySite.HostAPI
http://schemas.microsoft.com/claims/authnmethodsreferences: pwd
The same issue happens for Web API controllers as well.
Details
MySite is the namespace of my solution and is also what I have defined as a client in my appsettings.json file:
{
"IdentityServer": {
"Clients": {
"MySite": {
"Profile": "IdentityServerSPA"
}
}
}
}
My web application project's name is MySite.Host so MySite.HostAPI the name of the resource and scope that are automatically generated by calling AuthenticationBuilder.AddIdentityServerJwt().
... this method registers an <<ApplicationName>>API API resource with IdentityServer with a default scope of <<ApplicationName>>API and configures the JWT Bearer token middleware to validate tokens issued by IdentityServer for the app.
Research
According to a few answers on Stack Overflow, adding IdentityResources.Profile() resource via IIdentityServerBuilder.AddInMemoryIdentityResources() should do the trick but it looks like it's already available via the claims I posted above (scope: profile).
I nevertheless tried it but the result is that the authentication flow becomes broken: the redirect to the login page does not work.
All of the answers I've found also make a reference to a Config class like in this demo file which holds configurations that are mainly fed to IIdentityServerBuild.AddInMemory...() methods.
However, it seems that Microsoft.AspNetCore.ApiAuthorization.IdentityServer does most of this in its implementation and instead offers extendable builders to use.
From the IdentityServer documentation, I don't believe I need to add a Client because the access token already exists. The client ReactJS application uses the access_token from oidc-client to make authorised calls to my Web APIs.
It also doesn't appear like I need to add a Resource or Scope for the username information because I believe these already exist and are named profile. More to this point is that the documentation for "IdentityServerSPA" client profile states that:
The set of scopes includes the openid, profile, and every scope defined for the APIs in the app.
I also looked at implementing IProfileService because according to the documentation this is where additional claims are populated. The default implementation is currently being used to populate the claims that are being requested by the ProfileDataRequestContext.RequestedClaimTypes object and this mechanism already works because this is how the ReactJS client code receives them. This means that when I'm trying to get the user claims from ASP.NET-Core Identity, it's not properly populating ProfileDataRequestContext.RequestedClaimTypes or perhaps not even calling IProfileServices.GetProfileDataAsync at all.
Question
Considering that my project uses Microsoft.AspNetCore.ApiAuthorization.IdentityServer, how can I view the username from my ASP.NET-Core C# code, preferably with IHttpContextAccessor?
What you need to do is to extend the default claims requested by IdentityServer with your custom ones. Unfortunately, since you're using the minimalistic IdentityServer implementation by Microsoft, the correct way of making the client request the claims isn't easy to find. However, assuming you have only one application (as per the template), you could say that the client always wants some custom claims.
Very important first step:
Given your custom IProfileService called, say, CustomProfileService, after these lines:
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
you have to get rid of the implementation used in the scaffolded template, and use your own:
services.RemoveAll<IProfileService>();
services.AddScoped<IProfileService, CustomProfileService>();
Next, the actual implementation of the custom IProfileService isn't really hard if you start from Microsoft's version:
public class CustomProfileService : IdentityServer4.AspNetIdentity.ProfileService<ApplicationUser>
{
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory) : base(userManager, claimsFactory)
{
}
public CustomProfileService(UserManager<ApplicationUser> userManager,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
ILogger<ProfileService<ApplicationUser>> logger) : base(userManager, claimsFactory, logger)
{
}
public override async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
string sub = context.Subject?.GetSubjectId();
if (sub == null)
{
throw new Exception("No sub claim present");
}
var user = await UserManager.FindByIdAsync(sub);
if (user == null)
{
Logger?.LogWarning("No user found matching subject Id: {0}", sub);
return;
}
var claimsPrincipal = await ClaimsFactory.CreateAsync(user);
if (claimsPrincipal == null)
{
throw new Exception("ClaimsFactory failed to create a principal");
}
context.AddRequestedClaims(claimsPrincipal.Claims);
}
}
With those two steps in place, you can start tweaking CustomProfileService's GetProfileDataAsync according to your needs. Notice that ASP.NET Core Identity by default already has the email and the username (you can see these in the claimsPrincipal variable) claims, so it's a matter of "requesting" them:
// ....
// also notice that the default client in the template does not request any claim type,
// so you could just override if you want
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "email" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
And if you want to add custom data, for example, the users first and last name:
// ....
context.RequestedClaimTypes = context.RequestedClaimTypes.Union(new[] { "first_name", "last_name" }).ToList();
context.AddRequestedClaims(claimsPrincipal.Claims);
context.AddRequestedClaims(new[]
{
new Claim("first_name", user.FirstName),
new Claim("last_name", user.LastName),
});
User information can be retrieved via the scoped UserManager<ApplicationUser> service which is set up by the project template. The users's claims contains "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" (ClaimTypes.NameIdentifier) whose value is the user identifier. UserManager<>.FindByIdAsync() can then be used to retrieve the ApplicationUser associated with the user and which contains additional user information.
Note that this contacts the user store each time it's invoked. A better solution would be to have the extra user information in the claims.
First, explicitly add the IHttpContextAccessor service if you haven't already by calling services.AddHttpContextAccessor();
From within an arbitrary singleton service:
public class MyService
{
public MyService(
IHttpContextAccessor httpContextAccessor,
IServiceProvider serviceProvider
)
{
var nameIdentifier = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
using (var scope = serviceProvider.CreateScope())
{
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.FindByIdAsync(nameIdentifier);
// Can access user.UserName.
}
}
}
UserManager<ApplicationUser> can be accessed directly within Razor pages and Controllers because these are already scoped.
I have a solution consisting of:
ASP.NET Core 2.1 running IdentityServer4 on top of ASP.NET Identity Core.
ASP.NET Core 2.1 Web API set to use the IdentityServer as the authentication provider.
A React SPA web application using oidc-client javascript library.
When I create new users I set some custom claims that are saved in the AspNetUserClaims table which looks like this:
Then, on my API project, inside a controller I want to get those user claims of the authenticated user.
I was expecting this.User.Claims to get me those, but instead that's returning the following, which seem to be claims related to the client app, not the user.
How can I access those custom user claims (address, location, tenant_role) from a controller inside the Web API project?
Bare in mind that the API project doesn't have access to the UserManager class or anything ASP.NET Identity Core related.
So, I order for my custom user claim to be available in every API request I had to do the following when setting up the ApiResource on the IdentityServer startup.
//Config.cs
public static IEnumerable<ApiResource> GetApiResources()
{
ApiResource apiResource = new ApiResource("api1", "DG Analytics Portal API")
{
UserClaims =
{
JwtClaimTypes.Name,
JwtClaimTypes.Email,
AnalyticsConstants.TenantRoleClaim // my custom claim key/name
}
};
return new List<ApiResource>
{
apiResource
};
}
This method is passed to the services.AddInMemoryApiResources (or whatever storage method you're using)
IIdentityServerBuilder builder = services
.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources()) // here
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
With that setup, whenever an API endpoint is hit, my custom TenantRole claim is present so I can simply do User.FindFirst(AnalyticsConstants.TenantRoleClaim) to get it.
You'll need to define identity resources and scopes a la:
http://docs.identityserver.io/en/latest/topics/resources.html
And then ensure that they are exposed by your IProfileService or IClaimsService implementation in your identity server:
http://docs.identityserver.io/en/latest/reference/profileservice.html
They claims can either be included in the tokens themselves or be access via the user info endpoint as needed - this is sensible if your claim data is particularly large (i.e. in the 1000s of characters).
You can use ClaimsPrincipal.FindFirst() to access your customized claims.
Doc:
https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimsprincipal.findfirst?view=netcore-2.1
Example: User.FindFirst("your_claim_key").Value
I'm currently learning authentication mechanisms used in ASP.NET Core 2.0. Now, I'm trying to find what method is used for generating email confirmation token, however I cannot find the information neither in documentation nor by browsing the source code.
So far, I found out that DataProtectorTokenProvider is the class responsible for generating the token. This is the exact method which is used to create the token:
public virtual async Task<string> GenerateAsync(string purpose, UserManager<TUser> manager, TUser user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var ms = new MemoryStream();
var userId = await manager.GetUserIdAsync(user);
using (var writer = ms.CreateWriter())
{
// ... Here the token generation is performed
}
var protectedBytes = Protector.Protect(ms.ToArray());
return Convert.ToBase64String(protectedBytes);
}
The part which I cannot get is what is the default IDataProtectionProvider implementation, which is used in Protector.Protect(ms.ToArray()), and where it is registered. So my questions are:
What is the default class used for protecting email confirmation token?
Where is it registered?
Can it be substituted with custom implementation when needed? If yes, how can this be achieved?
Thanks!
In order to answer your specific 3 questions:
KeyRingBasedDataProtectionProvider (source), which creates instances of KeyRingBasedDataProtector (source).
The line that registers the default IDataProtectionProvider is here.
Try the approach offered in the answers over here: Replace service registration in ASP.NET Core built-in DI container?.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I am using IdentityServer4.
I want to add other custom claims to access token but I'm unable to do this. I have modified Quickstart5 and added ASP.NET Identity Core and the custom claims via ProfileService as suggested by Coemgen below.
You can download my code here: [zip package][3]. (It is based on: Quickstart5 with ASP.NET Identity Core and added claims via ProfileService).
Issue: GetProfileDataAsync does not executed.
You should implement your own ProfileService.
Have a look in this post which I followed when I implemented the same:
https://damienbod.com/2016/11/18/extending-identity-in-identityserver4-to-manage-users-in-asp-net-core/
Here is an example of my own implementation:
public class ProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public ProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
var claims = new List<Claim>
{
new Claim("FullName", user.FullName),
};
context.IssuedClaims.AddRange(claims);
}
public async Task IsActiveAsync(IsActiveContext context)
{
//>Processing
var user = await _userManager.GetUserAsync(context.Subject);
context.IsActive = (user != null) && user.IsActive;
}
}
Don't forget to configure the service in your Startup.cs (via this answer)
services.AddIdentityServer()
.AddProfileService<ProfileService>();
Ok the issue here is this:
although you have configured your available Identity resources correctly (both standard & custom), you also need to explicitly define which ones are a necessity when calling your api resource. In order to define this you must go to your Config.cs class on ExampleIdentityServer project and provide a third argument like on the new ApiResouirce constructor. Only those will be included into the access_token
// scopes define the API resources in your system
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API", new[] { JwtClaimTypes.Subject, JwtClaimTypes.Email, JwtClaimTypes.Phone, etc... })
};
}
In essence this means that I got my identity claims configured for my organization but there may be more than one APIs involved and not all of the APIs make use of all available profile claims. This also means that these will be present inside your ClaimsPrincipal all the rest can still be accessed through the "userinfo" endpoint as a normal http call.
NOTE: regarding refresh tokens:
If you chose to enable refresh tokens via AllowOfflineAccess = true, you may experience the same behavior upon refreshing the access_token "GetProfileDataAsync does not executed!". So the claims inside the access_token stay the same although you get a new access_token with updated lifetime. If that is the case you can force them to always refresh from the Profile service by setting UpdateAccessTokenClaimsOnRefresh=true on the client configuration.
Issue found.
In startup.cs, instead of adding services.AddTransient<IProfileService, ProfileService>();, add .AddProfileService<ProfileService>() to services.AddIdentityServer().
You will end up with
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<ProfileService>();
Thanks for Coemgen for helping out! Nothing wrong with the code, just the startup was wrong.
You can include any claim by using UserClaims option in your GetIdentityResources() in the config class :
UserClaims:
List of associated user claim types that should be included in the identity token. (As per the official documentation) http://docs.identityserver.io/en/release/reference/identity_resource.html#refidentityresource