Reputation: 788
I'm currently setting up a new project with a Web API and a MVC UI (will eventually have a mobile UI as well which can talk to the same API).
So, I have the following plan:
This means the identity server is just that, an identity server (and means I allow people to log in through Google, etc. without worrying about their roles and permissions).
So, to achieve the above, I need to check the user's permission on the API, NOT on the client (the client could be anything - web, phone app, JavaScript client, etc. so we can't rely on that to handle user permissions).
On the API, I have implemented a Permissionhandler and PermissionRequirement authorization policy. So, on the API controller or method, I can do something like: [Authorize(Policy = "CreateUser")]. It looks like I'll need to have a policy per system permission.
So in the authorisation handler I need to:
This was going well up until I tried to request the user's username from the identity server. I understand I need to use the UserInfoClient to do that, but I can't figure out how to use the user's token/credentials to get their claims from the identity server or to at least get their User.Identity.Name.
Now I could just use the sub ID to add the user to the application database, but then whoever's managing the permissions would have no idea who that person is, so I need to use their email really.
Now in the MVC client, I can see User.Identity.Name without any problems, but in the API that value is null!?
So my question is: how on earth do I get the current user's Identity.Name, username or email from within the API?
Thanks.
Upvotes: 3
Views: 2855
Reputation: 788
Think I've cracked it now.
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
if (mvcContext != null)
{
// Use the UserInfo endpoint to get the user's claims
var discoveryClient = new DiscoveryClient("http://localhost:5000");
var doc = await discoveryClient.GetAsync();
var accessToken = await mvcContext.HttpContext.Authentication.GetTokenAsync("access_token");
var userInfoClient = new UserInfoClient(doc.UserInfoEndpoint);
var response = await userInfoClient.GetAsync(accessToken);
var claims = response.Claims;
}
This is detailed right at the bottom of https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies so according to Microsoft, it's the right way.
You did however spark this idea, thank you Win. I had been staring at this bloody thing for hours. All you need sometimes is someone else to say something, anything, and the curse is broken! :)
I am still open to a better way of achieving this, if anyone has one.
Cheers
Upvotes: 3
Reputation: 62290
I believe you already configure IdentityServerAuthentication in Web API similar like this -
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://UrlOfIentityServer",
RequireHttpsMetadata = false,
ApiName = "exampleapi"
});
...
}
When you make a web service call, you will need to pass the same token received from IdentityServer like this -
using (var httpClient = new HttpClient())
{
string accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
httpClient.BaseAddress = new Uri("http://UrlOfWebAPI");
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return await httpClient.GetStringAsync("api/clock/time");
}
Upvotes: 2