Asp.net Core Authorize user with Policy - c#

I am trying to authorize an user with a bearer token send from the request header.
I added this code in startup file of resource server.
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
Here is my method in controller.
[Authorize("Bearer")]
[HttpGet]
[Route("list")]
public IEnumerable<Products> List()
{
string Authorization = Request.Headers["Authorization"];
}
Application showing me error 401 Unauthorized even if i had token
I am sending this Token in the header request
Authorization:Bearer "xyz"

To work with Bearer token you have to add the following code on your Configure method
public void Configure(IApplicationBuilder app) {
app.UseJwtBearerAuthentication(options => {
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.Audience = "OAuth:Audience";
options.Authority = "OAuth:Authority";
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
metadataAddress: options.Authority + ".well-known/openid-configuration",
configRetriever: new OpenIdConnectConfigurationRetriever(),
docRetriever: new HttpDocumentRetriever() { RequireHttps = false });
});
}
You will also need a middleware to handle the authorization process.
Have a look at AspNet.Security.OpenIdConnect.Server and OpenIddict

Related

How to use JwtBearer to authenticate SignalR client?

I have project to use SignalR from desktop application. So my application have 2 method authentication. the one is cookie for web, the one is jwt for signalr client. cause the client comes from desktop.
How to exactly authentication, i check header request, client sent
Authorization: Bearer testing
in server, i assigns the token to MessageReceivedContext.Token. so i think, it automatically handle by some magic in background by AuthorizeAttribute. the description of property Bearer Token. This will give the application an opportunity to retrieve a token from an alternative location.
Client.js
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/socket", {
accessTokenFactory: () => "testing"
})
.build();
connection.start();
Hub.cs
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class SocketHub : Hub
{
public override Task OnConnectedAsync()
{
return Task.Run(()
=> Console.WriteLine(Context.ConnectionId));
}
}
Program.cs
AddJwtBearer(options =>
{
options.Authority = "http://localhost:5000/socket";
options.RequireHttpsMetadata = false;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Headers.Authorization;
// If the request is for our hub...
if (!string.IsNullOrEmpty(accessToken))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});

Blazor WebAssembly 401 Unauthorized even when I am authorized

I am using Blazor WebAssembly Asp.Net Core hosted PWAand integrated the AspNetCore.Identity into it. I created the AuthenticationStateProvider in the Client-Side and now I want to allow the user access to a controller where he needs to be authorized.
I have tested via postman, the users were been created and stored in DB as aspnetusers with the right credentials. The Login/Account Controller work as I wanted it.
When the user is authorized it tells this exception in the browser when accessing the authorized controller request:
Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Response status code does not indicate success: 401 (Unauthorized).
System.Net.Http.HttpRequestException: Response status code does not
indicate success: 401 (Unauthorized).
Startup.cs (ConfigureServices-Method):
...
serviceCollection.AddDbContext<SQLiteTestDbContext>(options =>
{
options.UseSqlite(config["ConnectionStrings:SQLiteTestConnection"]);
});
serviceCollection.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<SQLiteTestDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
});
services.AddHttpContextAccessor();
services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
Program.cs Client-Side
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Logging.SetMinimumLevel(LogLevel.Warning);
//Registering Shared-Library models
builder.Services.AddScoped<ObjectModel>();
builder.Services.AddBlazoredLocalStorage();
builder.Services.AddAuthorizationCore();
builder.Services.AddScoped<AuthenticationStateProvider, ApiAuthenticationStateProvider>();
builder.Services.AddScoped<IAuthService, AuthService>();
//Registered BlazorContextMenu Service
builder.Services.AddBlazorContextMenu();
//Registering FileReader service, for image upload -> Azure
builder.Services.AddFileReaderService(options => options.UseWasmSharedBuffer = true);
builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
My Controller with authorize attribute:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class ObjectController : ControllerBase
{
....
Note:
When your user tries to access a protected (annotated with the Authorize attribute) page on the client he should login first or register.
In order to register, he should be redirected to an Account Controller where you should create a new user, and add it to the database (You said you " integrated the AspNetCore.Identity into it"), which is fine...and should be used to authenticate and verify the user's identity. You account controller should also produce a Jwt Token that should be passed to the client app, and stored in the local storage.
Now, whenever your user tries to access protected resources on your Web Api endpoints, you should retrieve the Jwt Token from the local storage, and add it to the request header. If you do so, the Unauthorized response would be something of the past.
Custom AuthenticationStateProvider can be a good place from which you can manage storing the Jwt Token in the local storage and retrieving it for outbound HTTP request calls.
Here's some sample code to clarify what you should do:
#code {
WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
var token = await TokenProvider.GetTokenAsync();
forecasts = await Http.GetJsonAsync<WeatherForecast[]>(
"api/WeatherForecast",
new AuthenticationHeaderValue("Bearer", token));
}
}
Note: TokenProvider is a custom AuthenticationStateProvider that defines a method called GetTokenAsync that provides (reading the Jwt Token from the local storage and passing it to the calling code) the Jwt Token
Hope this helps...
In case of Linux App Service in combination with ID Server the Authority needs to be set according to Microsoft documentation: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/identity-api-authorization?view=aspnetcore-5.0#azure-app-service-on-linux-1
services.Configure<JwtBearerOptions>(
IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
options =>
{
options.Authority = "{AUTHORITY}";
});
Example: options.Authority = "https://contoso-service.azurewebsites.net";

401 Unauthorized using IdentityServer3.AccessTokenValidation

When using IdentityServer3.AccessTokenValidation in my .Net 4.8 (not core) WebAPI to secure the endpoints, the WebAPI always returns a 401 Not Authorized to the client app. The token is supplied by IDS4. When the client is calling my Asp.Net Core API, Authorization is successful. But when calling the .Net 4.8 AspNet WebApi, it is not.
Client MVC App Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "TestMvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
options.Scope.Add("testApi");
options.CallbackPath = "/auth";
options.SignedOutRedirectUri = "~/home";
});
}
The .Net Core API Startup that works:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.ApiName = "testApi";
options.RequireHttpsMetadata = false;
options.RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role";
});
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Startup.cs of the .Net 4.8 WebApi that always returns 401:
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
config.MapHttpAttributeRoutes();
config.EnableSystemDiagnosticsTracing();
var cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
var idsOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
RequiredScopes = new string[] { "testApi" },
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role
};
app.UseIdentityServerBearerTokenAuthentication(idsOptions);
app.UseWebApi(config);
}
The Asp.Net Core Controller Endpoint that executes successfully
[Route("test")]
[Authorize(Roles = "[A role claim that is definitely present in access token]")]
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok("Success");
}
}
The Asp.Net 4.8 Controller Endpoint that returns 401
[Route("test")]
[Authorize(Roles = "[A role claim that is definitely present in access token]")]
public class TestController : ApiController
{
[HttpGet]
public IHttpActionResult Get()
{
return Ok("Success");
}
}
MVC Client App Calling API:
public async Task<IActionResult> AccessApi()
{
var accessToken = await HttpContext.GetTokenAsync("access_token");
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = await client.GetStringAsync("http://localhost:5001/test");
ViewBag.Json = content;
return View("json");
}
The last lines of the IDS server log:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 GET http://localhost:5000/.well-known/openid-configuration dbug:
IdentityServer4.Hosting.EndpointRouter[0]
Request path /.well-known/openid-configuration matched to endpoint type Discovery dbug:
IdentityServer4.Hosting.EndpointRouter[0]
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint info:
IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for
/.well-known/openid-configuration dbug:
IdentityServer4.Endpoints.DiscoveryEndpoint[0]
Start discovery request info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 17.3987ms 200 application/json; charset=UTF-8 info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 GET http://localhost:5000/.well-known/openid-configuration/jwks dbug:
IdentityServer4.Hosting.EndpointRouter[0]
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery dbug:
IdentityServer4.Hosting.EndpointRouter[0]
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint info:
IdentityServer4.Hosting.IdentityServerMiddleware[0]
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for
/.well-known/openid-configuration/jwks dbug:
IdentityServer4.Endpoints.DiscoveryKeyEndpoint[0]
Start key discovery request info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 16.6027ms 200 application/jwk-set+json; charset=UTF-8
The token is issued successfully and passed in the request headers to the API.
The API seems to be calling the discover endpoint correctly, but then nothing after that. I would expect the API to validate the incoming token with the IDS server, but there is no call to the IDS server for validation. Confirmed with both the IDS log and using Fiddler to check the traffic.
This can be caused by couple of things, to be sure we need the log of ASP.NET API project. Here is some suggested fixes for similar issues:
On Statup.cs class of IdentityServer project
Change AccessTokenJwtType to JWT, default value on IdentityServer4 is at+jwt but .Net Framework Api (OWIN/Katana) requires JWT.
Add /resources aud by setting EmitLegacyResourceAudienceClaim to true, this is removed on IdentityServer4.
You can verify the access_token on https://jwt.ms/ by checking "typ" and "aud" .
var builder = services.AddIdentityServer(
options =>
{
options.AccessTokenJwtType = "JWT";
options.EmitLegacyResourceAudienceClaim = true;
});
On Statup.cs class of .Net Framework Api project, set ValidationMode to ValidationMode.Local, access token validation endpoint used by this method is removed on IdentityServer4.
var idsOptions = new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://localhost:5000",
RequiredScopes = new string[] { "testApi" },
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
ValidationMode = ValidationMode.Local,
};
I have sample working implementation here, and also a blog post contains more details https://nahidfa.com/posts/identityserver4-and-asp-.net-web-api/
I strongly suggest you to gather logs on API, this helps to find the actual issue in your case and finding the fix. here is a sample to turn on OWIN log on Api.

.NET Core Authorization - Constant 403 on JWT Bearer

I'm attempting to authorize requests to my API which bear a JWT token attached to it, however, none of the tutorials, blog posts, and documentation have helped avoiding a constant 403 - Unauthorized error.
This is the -skimmed- current configuration:
Class which generates the token: TokenManagement.cs:
// Add the claims to the token
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, credentials.Username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("claimName", "claimValue")
};
Configuring the services: Startup.cs - ConfigureServices():
services.Configure<GzipCompressionProviderOptions>(options => options.Level = System.IO.Compression.CompressionLevel.Optimal);
services.AddResponseCompression();
services.AddAuthentication()
.AddJwtBearer(config => {
config.RequireHttpsMetadata = false;
config.SaveToken = true;
config.TokenValidationParameters = new TokenValidationParameters()
{
ValidIssuer = "Issuer",
ValidAudience = "Audience",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(symmetricKey))
};
});
services.AddAuthorization(options => {
options.AddPolicy("myCustomPolicy", policy => {
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim("claimName", "claimValue");
});
});
services.AddMvc();
General Configuration: Startup.cs - Configure():
app.UseAuthentication();
app.Use(async (context, next) => {
await next();
if (context.Response.StatusCode == 404 &&
!Path.HasExtension(context.Request.Path.Value)) {
context.Request.Path = "/index.html";
await next();
}
});
app.UseMvc();
app.UseResponseCompression();
app.UseDefaultFiles();
app.UseStaticFiles();
Controller which should be authorized: ActionsController.cs:
[Authorize(Policy = "myCustomPolicy")]
[Route("api/[controller]")]
public class ActionsController : Controller
Any request I send to the server (which carries a JWT token with the proper claim), returns as a 403.
Any methods which have the [AllowAnonymous] attribute, work just fine.
Is there a way to -at least- debug and see what's going on?
I found out that some claim types changed to different values from my identity server config.
for example , In my Identity Server i am using role claim type:
UserClaims = new []
{
JwtClaimTypes.Role , user.role // "JwtClaimTypes.Role" yield "role"
};
But when i debuged my web api , the role claim type has changed to (see my snapshot below, under watch section):
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"
Solution:
To "workaround" (is this desired behavior?) the issue, you need to check your claim type
value are planning use in web api, and use the correct claim type value in your policy.
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdmin", policy =>
{
//policy.RequireClaim(IdentityModel.JwtClaimTypes.Role, "Admin"); // this doesn't work
policy.RequireClaim(ClaimTypes.Role, "Admin"); // this work
});
});
my web api debug snapshot:
Try to enable CORS in Startup.cs File
public void ConfigureAuth(IAppBuilder app) {
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
// Rest of code
}

ASP.NET Core WebAPI Cookie + JWT Authentication

we have a SPA (Angular) with API backend (ASP.NET Core WebAPI):
SPA is listens on app.mydomain.com, API on app.mydomain.com/API
We use JWT for Authentication with built-in Microsoft.AspNetCore.Authentication.JwtBearer; I have a controller app.mydomain.com/API/auth/jwt/login which creates tokens. SPA saves them into local storage. All works perfect. After a security audit, we have been told to switch local storage for cookies.
The problem is, that API on app.mydomain.com/API is used by SPA but also by a mobile app and several customers server-2-server solutions.
So, we have to keep JWT as is, but add Cookies. I found several articles which combines Cookies and JWT on different controllers, but I need them work side-by-side on each controller.
If client sends cookies, authenticate via cookies. If client sends JWT bearer, authenticate via JWT.
Is this achievable via built-in ASP.NET authentication or DIY middleware?
Thanks!
Okay, I have been trying achieving this for a while and i solved same issue of using jwt Authentication Tokens and Cookie Authentication with the following code.
API Service Provider UserController.cs
This Provide Different Services to the User with Both (Cookie and JWT Bearer)Authentication Schemes
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
[Route("[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly IUserServices_Api _services;
public UsersController(IUserServices_Api services)
{
this._services = services;
}
[HttpGet]
public IEnumerable<User> Getall()
{
return _services.GetAll();
}
}
My Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options => {
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.LoginPath = "/Account/Login";
options.AccessDeniedPath = "/Home/Error";
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = " you site link blah blah",
ValidIssuer = "You Site link Blah blah",
IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(sysController.GetSecurityKey()))
,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
}
And further if you want custom Authentication for a specific Controller
then you have to specify the Authentitcation Type for the Authorization
like:
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View(); // This can only be Access when Cookie Authentication is Authorized.
}
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View(); // And this one will be Access when JWT Bearer is Valid
}
I've been having the same issue and i just found what it seems to be the solution in another question here in stackoverflow.
Please take a look at this.
I'll try that solution myself and update this answer with the results.
Edit: It seems it's not possible to achieve double authentication types in a same method but the solution provided in the link i mentioned says:
It's not possible to authorize a method with two Schemes Or-Like, but you can use two public methods, to call a private method
//private method
private IActionResult GetThingPrivate()
{
//your Code here
}
//Jwt-Method
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme}")]
[HttpGet("bearer")]
public IActionResult GetByBearer()
{
return GetThingsPrivate();
}
//Cookie-Method
[Authorize(AuthenticationSchemes = $"{CookieAuthenticationDefaults.AuthenticationScheme}")]
[HttpGet("cookie")]
public IActionResult GetByCookie()
{
return GetThingsPrivate();
}
Anyway you should take a look at the link, it sure helped me.
Credit goes to Nikolaus for the answer.
I have not been able to find much information on a good way to do this - having to duplicate the API is a pain just to support 2 authorization schemes.
I have been looking into the idea of using a reverse proxy and it looks to me like a good solution for this.
User signs into Website (use cookie httpOnly for session)
Website uses Anti-Forgery token
SPA sends request to website server and includes anti-forgery token in header: https://app.mydomain.com/api/secureResource
Website server verifies anti-forgery token (CSRF)
Website server determines request is for API and should send it to the reverse proxy
Website server gets users access token for API
Reverse proxy forwards request to API: https://api.mydomain.com/api/secureResource
Note that the anti-forgery token (#2,#4) is critical or else you could expose your API to CSRF attacks.
Example (.NET Core 2.1 MVC with IdentityServer4):
To get a working example of this I started with the IdentityServer4 quick start Switching to Hybrid Flow and adding API Access back. This sets up the scenario I was after where a MVC application uses cookies and can request an access_token from the identity server to make calls the API.
I used Microsoft.AspNetCore.Proxy for the reverse proxy and modified the quick start.
MVC Startup.ConfigureServices:
services.AddAntiforgery();
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MVC Startup.Configure:
app.MapWhen(IsApiRequest, builder =>
{
builder.UseAntiforgeryTokens();
var messageHandler = new BearerTokenRequestHandler(builder.ApplicationServices);
var proxyOptions = new ProxyOptions
{
Scheme = "https",
Host = "api.mydomain.com",
Port = "443",
BackChannelMessageHandler = messageHandler
};
builder.RunProxy(proxyOptions);
});
private static bool IsApiRequest(HttpContext httpContext)
{
return httpContext.Request.Path.Value.StartsWith(#"/api/", StringComparison.OrdinalIgnoreCase);
}
ValidateAntiForgeryToken (Marius Schulz):
public class ValidateAntiForgeryTokenMiddleware
{
private readonly RequestDelegate next;
private readonly IAntiforgery antiforgery;
public ValidateAntiForgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery)
{
this.next = next;
this.antiforgery = antiforgery;
}
public async Task Invoke(HttpContext context)
{
await antiforgery.ValidateRequestAsync(context);
await next(context);
}
}
public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder UseAntiforgeryTokens(this IApplicationBuilder app)
{
return app.UseMiddleware<ValidateAntiForgeryTokenMiddleware>();
}
}
BearerTokenRequestHandler:
public class BearerTokenRequestHandler : DelegatingHandler
{
private readonly IServiceProvider serviceProvider;
public BearerTokenRequestHandler(IServiceProvider serviceProvider, HttpMessageHandler innerHandler = null)
{
this.serviceProvider = serviceProvider;
InnerHandler = innerHandler ?? new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
request.Headers.Authorization =new AuthenticationHeaderValue("Bearer", accessToken);
var result = await base.SendAsync(request, cancellationToken);
return result;
}
}
_Layout.cshtml:
#Html.AntiForgeryToken()
Then using your SPA framework you can make a request. To verify I just did a simple AJAX request:
<a onclick="sendSecureAjaxRequest()">Do Secure AJAX Request</a>
<div id="ajax-content"></div>
<script language="javascript">
function sendSecureAjaxRequest(path) {
var myRequest = new XMLHttpRequest();
myRequest.open('GET', '/api/secureResource');
myRequest.setRequestHeader("RequestVerificationToken",
document.getElementsByName('__RequestVerificationToken')[0].value);
myRequest.onreadystatechange = function () {
if (myRequest.readyState === XMLHttpRequest.DONE) {
if (myRequest.status === 200) {
document.getElementById('ajax-content').innerHTML = myRequest.responseText;
} else {
alert('There was an error processing the AJAX request: ' + myRequest.status);
}
}
};
myRequest.send();
};
</script>
This was a proof of concept test so your mileage may very and I'm pretty new to .NET Core and middleware configuration so it could probably look prettier. I did limited testing with this and only did a GET request to the API and did not use SSL (https).
As expected, if the anti-forgery token is removed from the AJAX request it fails. If the user is has not logged in (authenticated) the request fails.
As always, each project is unique so always verify your security requirements are met. Please take a look at any comments left on this answer for any potential security concerns someone might raise.
On another note, I think once subresource integrity (SRI) and content security policy (CSP) is available on all commonly used browsers (i.e. older browsers are phased out) local storage should be re-evaluated to store API tokens which will lesson the complexity of token storage. SRI and CSP should be used now to help reduce the attack surface for supporting browsers.
I think the easiest solution is one proposed by David Kirkland:
Create combined authorization policy (in ConfigureServices(IServiceCollection services)):
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder =
defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
And add middleware that will redirect to login in case of 401 (in Configure(IApplicationBuilder app)):
app.UseAuthentication();
app.Use(async (context, next) =>
{
await next();
var bearerAuth = context.Request.Headers["Authorization"]
.FirstOrDefault()?.StartsWith("Bearer ") ?? false;
if (context.Response.StatusCode == 401
&& !context.User.Identity.IsAuthenticated
&& !bearerAuth)
{
await context.ChallengeAsync("oidc");
}
});
while looking for combined firebase authorization with net core web api (cookie for web site and authorization header for mobile app ) end with the following solution.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://securetoken.google.com/xxxxx";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = options.Authority,
ValidateAudience = true,
ValidAudience = "xxxxx",
ValidateLifetime = true
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Cookies.ContainsKey(GlobalConst.JwtBearer))
{
context.Token = context.Request.Cookies[GlobalConst.JwtBearer];
}
else if (context.Request.Headers.ContainsKey("Authorization"))
{
var authhdr = context.Request.Headers["Authorization"].FirstOrDefault(k=>k.StartsWith(GlobalConst.JwtBearer));
if (!string.IsNullOrEmpty(authhdr))
{
var keyval = authhdr.Split(" ");
if (keyval != null && keyval.Length > 1) context.Token = keyval[1];
}
}
return Task.CompletedTask;
}
};
});
where
public static readonly string JwtBearer = "Bearer";
seems working fine.
checked it from mobile & postman (for cookie )
with this code you can use cookie and header in the same time.
if cookie is null then check the header automatically.
add this code in AddJwtBearer options.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["Authorization"];
return Task.CompletedTask;
}
};
full usage is:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = Configuration["JwtToken:Audience"],
ValidIssuer = Configuration["JwtToken:Issuer"],
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtToken:Key"]))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["Authorization"];
return Task.CompletedTask;
}
};
});
Header => Authorization: Bearer Your-Token
or
Cookie => Authorization=Your-Token
//dont add Bearer in Cookie
I found this nice article by Rick Strahl. It is the best solution that I found so far and I used it in .NET 5
Here is the key code in order to combining JWT token and cookie authentication in .NET applications:
services.AddAuthentication(options =>
{
options.DefaultScheme = "JWT_OR_COOKIE";
options.DefaultChallengeScheme = "JWT_OR_COOKIE";
})
.AddCookie( options =>
{
options.LoginPath = "/login";
options.ExpireTimeSpan = TimeSpan.FromDays(1);
})
.AddJwtBearer( options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.JwtToken.Issuer,
ValidateAudience = true,
ValidAudience = config.JwtToken.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config.JwtToken.SigningKey))
};
})
.AddPolicyScheme("JWT_OR_COOKIE", "JWT_OR_COOKIE", options =>
{
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
return JwtBearerDefaults.AuthenticationScheme;
return CookieAuthenticationDefaults.AuthenticationScheme;
};
});
ASP.NET Core 2.0 Web API
Please follow this post for implementing JWT Token based authentication
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
If you are using visual studio make sure apply the Bearer type athentication type with the filter
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
for controller or actions.

Categories

Resources