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
Related
I have a multi-tenant .NET Core web app where the current user's tenant is resolved via middleware. In particular, tenants are resolved with a library called SaasKit.Multitenancy.
To use this library, you put this line in ConfigureServices():
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// (omitted for brevity)
// The 'Tenant' type is what you resolve to, using 'ApplicationTenantResolver'
services.AddMultitenancy<Tenant, ApplicationTenantResolver>();
// ...
}
And you put this line in Configure() to add it to the middleware pipeline:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseAuthentication();
app.UseMultitenancy<Tenant>(); //this line
app.UseMvc(ConfigureRoutes);
// ...
}
This causes the following method in the middleware to be executed, which resolves the current user's tenant:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
//whatever you need to do to figure out the tenant goes here.
}
This allows the result of this method (whichever tenant is resolved) to be injected into any class you want, like so:
private readonly Tenant _tenant;
public HomeController(Tenant tenant)
{
_tenant = tenant;
}
Up until now, we have been authenticating users with the .NET Identity platform, storing user data in our app's database. However, a new tenant of ours wants to be able to authenticate their users via SSO.
I have figured out most of the SSO stuff--I am using Azure AD to sign in users, and my organization's Azure AD tenant will be able to federate with their Identity Provider. In short, this code in ConfigureServices adds the Identity and AzureAD authentication:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// rest of the code is omitted for brevity
services.AddIdentity<ApplicationUser, ApplicationRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 12;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => _configuration.Bind("AzureAd", options)).AddCookie();
// policy gets user past [Authorize] if they are signed in with Identity OR Azure AD
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder(
AzureADDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme
).RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
When using Identity, I have been able to resolve the users's tenant with the UserManager, like so:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
TenantContext<Tenant> tenantContext = new TenantContext<Tenant>(new ApplicationTenant());
if (context.User.Identity.IsAuthenticated)
{
var user = await _userManager.Users
.Include(x => x.Tenant)
.FirstOrDefaultAsync(x => x.UserName == email);
if (user?.Tenant != null)
{
tenantContext = new TenantContext<Tenant>(user.Tenant);
_logger.LogDebug("The current tenant is " + user.Tenant.Name);
return await Task.FromResult(tenantContext);
}
}
return await Task.FromResult(tenantContext);
}
My plan was to modify this code so grabbed the current User's claims, which can be used to infer which tenant the user belongs to. However, when authenticating a user via Azure AD, HttpContext.User is always empty in the middleware, despite the user being signed in. It's not null, but HttpContext.User.Identity.IsAuthenticated is always false and HttpContext.User.Claims is empty. I only see the value of HttpContext.User populated once routing is complete and the code has reached a Controller.
I have tried reorganizing the middleware in pretty much every feasible way to no avail. What's confusing to me is that HttpContext.User is populated in the tenant resolver when the user is authenticated with Identity. With this in mind, I'm not sure how I can access the user's claims in the middleware when authenticating via Azure AD.
The best solution I can think of is to modify every instance the current tenant is injected into the code with a call to a method that resolves the tenant via claims. If the tenant is null in an area restricted with the [Authorize] attribute, it would imply the user is signed in via Azure AD, which would allow me to look at their claims. However, it really bothers me that I can't access the user's claims in the middleware, as I'm not sure what's really going on here.
Since you are trying to access HttpContext from a custom component you are going to want to add HttpContextAccessor to your service collection as follows:
services.AddHttpContextAccessor();
You can now resolve HttpContext as needed using dependency injection. This may or may not be helpful depending on how much control you have of the middleware that you are using.
For what it's worth, I've had little issue authenticating against AAD just using MSAL without additional third-party middleware. Good luck!
I suspect you might be running on .NET Core 2.0 and running into this issue.
I have an existing REST API running on ASP.NET Core 3.0. It uses MVC filter to perform an authorization check based on a header value and returns error in case of authorization failure so that the request is not passed to the controller.
Now, I am experimenting with gRPC and trying to port this API to a gRPC service. However, I do not see any obvious solutions that might act as an MVC filter replacement.
Is there some way to achieve similar authorization checking functionality, perhaps using metadata?
For MVC and gRpc, they are different. ActionFilter is not exist under gRpc.
If you want to apply checking request header for all actions, you could try implement your custom middleware before app.UseEndpoints and check the request header.
For another way, you could try Policy like below:
GrpcRequireemnt and GrpcHandler
public class GrpcRequireemnt : IAuthorizationRequirement
{
}
public class GrpcHandler : AuthorizationHandler<GrpcRequireemnt>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public GrpcHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, GrpcRequireemnt requirement)
{
var headers = _httpContextAccessor.HttpContext.Request.Headers;
StringValues token;
if (!headers.TryGetValue("token", out token))
{
context.Fail();
return Task.CompletedTask;
}
context.Succeed(requirement);
return Task.CompletedTask;
}
}
Register required services
services.AddAuthorization(options =>
{
options.AddPolicy("TokenAuthorize", policy =>
{
policy.AddRequirements(new GrpcRequireemnt());
});
});
services.AddHttpContextAccessor();
services.AddSingleton<IAuthorizationHandler, GrpcHandler>();
UseCase
[Authorize("TokenAuthorize")]
public override Task<BuyTicketsResponse> BuyTickets(BuyTicketsRequest request, ServerCallContext context)
{
var user = context.GetHttpContext().User;
return Task.FromResult(new BuyTicketsResponse
{
Success = _ticketRepository.BuyTickets(user.Identity.Name!, request.Count)
});
}
This will have slightly different answers depending on if you're using Grpc.Core, which is a wrapper around the C GRPC library initially developed at Google, which has been available for a while and supports a variety of .Net targets (including Framework), or if you're using the new Grpc.AspNetCore which launched with .Net Core 3.0 and is built on Kestrel and ASP.NET Core internals.
Grpc.Core
For Grpc.Core you would want to pass your header value as metadata, and then create a server-side Interceptor to handle the metadata and the request. You can also consider using the AsyncAuthInterceptor, however the core Grpc implementation on the client side will not send credentials over insecure (non-TLS) connections.
Grpc.AspNetCore
Grpc.AspNetCore is built on ASP.NET and can use ASP.NET middleware, including the default ASP.NET authentication. If you can convert your filter into a middleware, you would be able to share the authentication between both implementations.
We are trying to understand what is the expected handling of a ChallengeResult when there are multiple authentication schemes registered.
We need to handle such a scenario because we have an ASP.NET core 2.2 app exposing some action methods (we use the MVC middleware) that must be used by an angularjs SPA which relies on cookies authentication and some third parties applications which use an authentication mechanism based on the Authorization HTTP request header. Please notice that the involved action methods are the same for both the users, this means that each one of them must allow authentication using both the cookie and the custom scheme based on Authorization HTTP request header. We know that probably this is not an optimal design but we cannot modify the overall architecture.
This documentation seems to confirm that what we would like to achieve is entirely possible using ASP.NET core 2.2. Unfortunately, the cookie authentication used by the UI app and the custom authentication used by the third parties must behave differently in case of an authentication challenge and their expected behaviors are not compatible with each other: the UI app should redirect the user to a login form, while a thir party application expects a raw 401 status code response. The documentation linked above does not offer a clear explanation of the ChallengeResult handling, so we decided to experiment with a test application.
We created two fake authentication handlers:
public class FooAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Foo failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
public class BarAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Bar failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
We registered the authentication schemas inside ConfigureServices method as follows:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "Bar";
options.AddScheme<FooAuthenticationHandler>("Foo", "Foo scheme");
options.AddScheme<BarAuthenticationHandler>("Bar", "Bar scheme");
});
}
This is our middleware pipeline:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
and finally we created a controller with an action method requiring authentication:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(AuthenticationSchemes = "Foo,Bar")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
We noticed that:
both the FooAuthenticationHandler and BarAuthenticationHandler are called to handle the ChallengeResult
the order is FooAuthenticationHandler before BarAuthenticationHandler and depends on the Authorize attribute (if you swap the authentication schemes inside the Authorize attribute then BarAuthenticationHandler is called first)
the caller gets a raw 500 status code response, but this only depends on the order in which the authorization handlers are called
the call to options.DefaultChallengeScheme = "Bar"; matters if and only if inside the [Authorize] attribute the property AuthenticationSchemes is not set. If you do so, only the BarAuthenticationHandler is called and FooAuthenticationHandler never gets a chance to authenticate the request or handle an authentication challenge.
So, the question basically is: when you have such a scenario, how are you expected to handle the possible "incompatibility" of different authentication schemes regarding ChallengeResult handling since they get both called ?
In our opinion is fine that both have a chance to authenticate the request, but we would like to know if it is possible to decide which one should handle the authentication challenge.
Thanks for helping !
You should not specify the schemes on the Authorize attribute.
Instead, specify one scheme as the default, and setup a forward selector.
The implementation of the selector depends on your case, but usually you can somehow figure out which scheme was used in a request.
For example, here is an example from the setup of an OpenID Connect scheme.
o.ForwardDefaultSelector = ctx =>
{
// If the current request is for this app's API
// use JWT Bearer authentication instead
return ctx.Request.Path.StartsWithSegments("/api")
? JwtBearerDefaults.AuthenticationScheme
: null;
};
So what it does is forward challenges (and well, everything) to the JWT handler if the route starts with /api.
You can do any kind of checks there, headers etc.
So in this case OpenID Connect and Cookies are setup as defaults for everything, but if a call is received that is going to the API, use JWT authentication.
The example here forwards all the "actions" you can do with authentication (challenge, forbid etc.).
You can also setup forward selectors for just challenges etc.
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
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);
}
}