I am struggling to find an exact way of validating my OAuth bearer token which is passed when a request is sent to the API am working on which is a Asp.Net core project.
The purpose here is to extract the bearer token and Validate it and if all is fine then continue with the request.
So far my findings have come across the following
JWT bear token authorization which mostly talks about access_token
Asp.Net core security middleware
Custom Authorize attribute which handle this.
I am not really sure how I can achieve my validation? Should I extract the bearer token and then create a custom validating method?
Ideally would like the [Authorize] attribute to handle this.
Suggestions please?
Well finally after more research I finally found that custom AuthorizationHandler is a more suitable solution as suppose to using custom Authorize attributes which is not suggested in Asp.Net Core.
It was simple to setup and I am able to extract my Bearer token from the header for further authorization with OAuth.
Here is a my approach:
public class CustomAuthorizationHandler: IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
if (authHeader != null && authHeader.Contains("Bearer"))
{
var token = authHeader.Replace("Bearer", "");
// Now token can be used for further authorization
}
throw new NotImplementedException();
}
}
Lastly registering the handler in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
}
I think to put the following code snippet inside ConfigureServices() should be able to validate your access_token after installing Microsoft.AspNetCore.Authentication.JwtBearer NuGet package:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//options.SaveToken = true;
options.MetadataAddress = ValidationEndPoint;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = tokenIssuer,
ValidAudiences = new[] { clientId },
ValidAudience = null
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("MyPolicy", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
remember to put app.UseAuthentication() and app.UseAuthorization() in the Configure() method. And add [authorize] to your controller API.
Related
I wrote an ASP.NET Core 3.1 API which uses the JWTBearer authentication system. This system works well when I call the API from Postman, but I can't figure out how to call it throught my own application or ASP.NET Core 3.1 MVC Website. Here is the configuration of the API :
API Configuration :
In the Startup.cs ConfigrationServices method I added this classical piece of code :
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
};
});
Then I added the middleware app.UseAuthentication(); to the Configure method.
Now I have a UsersController.cs with a SignIn method which returns a JWT as string if the credentials are corrects.
Finally, I added a simple GetUsers() method with an [Authorize] tag to test the JWT authentication as following :
// GET: api/Users
[Authorize(Roles = "Administrator")]
[HttpGet]
public async Task<ActionResult<IEnumerable<User>>> GetUsers()
{
return await _context.Users.ToListAsync();
}
Throught Postman, everything works fine. I call the api/Users/SignIn url in POST passing my credentials as JSON. I get back my token in response with the 200 StatusCode.
Then I call the api/Users in GET passing the JWT previously obtained to the Postman settings Authorization > Type : Bearer Token. My API returns a successful code with all the data I asked for. Everything works as expected at the API side.
MVC Website Configuration :
To simplify the discussion with the API, I wrote a Class Library with a ClientService.cs class.
Int the ClientService.cs, I have this simplified piece of code which successfully gets the data from the API :
public async Task<string> GetPage(string model)
{
var request =
new HttpRequestMessage(
HttpMethod.Get,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
If I use it, let's say to get the informations from api/something as following : var smth = await GetPage("something") or any other AllowAnonymous method, it will properly works.
But if I want to make it working with an Authorize method, I actually add this piece of code just before sending the request :
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
Where the token variable contains my JWT hardwritten in code for testing purpose. Everything works fine too.
So now I'm obviously trying to avoid hardwritting the JWT. I decided to store it on the client side in an HttpOnly Cookie coupled with the AntiForgeryToken native function from ASP.NET Core. I wrote this code to store the cookie :
private void Authenticate(string token)
{
Response.Cookies.Append(
"JWT",
token,
new CookieOptions()
{
HttpOnly = true,
Secure = true,
}
);
}
And now I'm stucked here. Because I use it as a Service, my ClientService is the same for all my users. So I can't store the token somewhere and pass it to the newly created client for each request.
I tried to add the JWT to the header before calling the ClientService as following :
public async Task<ActionResult> Index()
{
Request.Headers.Add("Authorization", "Bearer " + Request.Cookies["JWT"]);
var users = await ClientService.GetUsersAsync();
//GetUsersAsync() simply call GetPage("users") method and deserialize the JSON returned as a List<User>
return View(users);
}
But because my ClientService create its own client which sends its own request each time I ask for some data from the API with this code :
var request =
new HttpRequestMessage(
HttpMethod.Get,,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
The headers I added before aren't passed to the API.
A simple solution could be to rewrite all my ClientService methods to accept a Token parameter but it seems redundant and painful.
Which is the best and simpliest solution to pass the token to my API ?
The case you are describing is actually Cookie Authentication :
1. Sign in
ClaimsIdentity claimsIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
//Not mandatory: you can add the token to claim for future usage, like API request from server to server
claimsIdentity.AddClaim(new Claim("token", tokenId));
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
2. Add Cookie authentication
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = true;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
};
}).AddCookieAuthentication();
AddCookieAuthentication looks like:
public static AuthenticationBuilder AddCookieAuthentication(this IServiceCollection services)
{
var authBuilder = services.AddAuthentication(sharedOptions =>
{
sharedOptions.RequireAuthenticatedSignIn = false;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts =>
{
opts.CookieManager = new ChunkingCookieManager();
opts.Cookie = new CookieBuilder()
{
Domain = "CookieDomain",
Name = "CookieName",
Path = "CookiePath",
SecurePolicy = CookieSecurePolicy.Always,
HttpOnly = true,
SameSite = SameSiteMode.Lax,
};
opts.ExpireTimeSpan = TimeSpan.FromMinutes(20);
opts.ForwardDefaultSelector = ctx =>
{
var authHeader = ctx.Request.Headers["Authorization"].FirstOrDefault();
if (authHeader?.StartsWith(JwtBearerDefaults.AuthenticationScheme) == true)
{
return JwtBearerDefaults.AuthenticationScheme;
}
else
{
return CookieAuthenticationDefaults.AuthenticationScheme;
}
};
});
return authBuilder;
}
A browser through which the user signed-in will receive the cookie cookie created in SignIn method and will be able to populate request via client as well.
Ok I figured it out myself, here is how I solved the problem :
1) On Startup.cs, add an HttpContextAccessor :
The Context from our ASP.NET Core website is only accessible by the controllers. The Class libraries can't access it.
To make it available to a class library, we need to use an HttPContextAccessor. It's a service we can use as a dependancy injection to our class library. We just have to add
services.AddHttpContextAccessor();
In the ConfigureServices method from the Startup.cs.
2) Adapt the Class Library
The ClientService needs to be able to receive the Dependancy Injection as following :
public class ClientService : IClientService
{
private readonly IHttpClientFactory _clientFactory;
private readonly IHttpContextAccessor _contextAccessor;
private readonly string _baseAddress = $#"https://localhost:[PORT]/api/";
public ClientService(IHttpClientFactory clientFactory, IHttpContextAccessor contextAccessor)
{
_clientFactory = clientFactory;
_contextAccessor = contextAccessor;
}
}
Now we can access the current context from the HttpContextAccessor _contextAccessor variable.
Next we have to modify our GetPage(string model) method :
public async Task<string> GetPage(string model)
{
var request =
new HttpRequestMessage(
HttpMethod.Get,
_baseAddress +
model);
var client = _clientFactory.CreateClient();
var authorization = _contextAccessor.HttpContext.Request.Headers.FirstOrDefault(x => x.Key == "Authorization").Value.FirstOrDefault();
if(authorization != null)
{
client.DefaultRequestHeaders.Add("Authorization", authorization);
}
var response = await client.SendAsync(request);
return await response.Content.ReadAsStringAsync();
}
The code added will try to get the "Authorization" header from the request and if it's not null, we add it to our own request. This allows the request to the API with the JWT.
3) Add a middleware to add the JWT to each request
Now the final step is to add the JWT to each request from our ASP.NET Core application. If needed, the JWT will be consumed by our Class Library without changing any piece of code, either from the library or from the application.
In order to realise that, we have to add a middleware to the Startup.cs > Configure() method.
The middleware looks like that :
// Add header:
app.Use((context, next) =>
{
var jwt = context.Request.Cookies["JWT"];
if(jwt != null)
{
context.Request.Headers.Add("Authorization", "Bearer " + jwt);
}
return next.Invoke();
});
This just reads the Cookie from the request headers and if the JWT HttpOnly Cookie isn't null, we add the token to our request. This request will then be captured by our Class Library in order to use it to talk with the API.
A few things to note :
Adding the JWT to each request even if we don't need it could consume more bandwidth overtime. It could be also less safe (I'm not a security expert)
Adding the JWT to our main request, then create a local client with its own request in our class library seems not optimal. But it works as expected and I'm actually unaware of how to optimize it.
Feel free to point any issue in my solution or modify it to add some clarifications or even other more efficient ways to achieve it.
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";
I have been making a web application using ASP.NET Core 3.1 using clean architecture.
I have some class libraries like Infrastructure, Persistence, Domain, Application, and a MVC application project named "Web" as the startup point of my application.
In the Web layer I have "Areas" in which I have an Admin area containing some controllers and action methods which return JSON as my API endpoints to be used in a React-based app.
I also have some controllers in the Web MVC project in Controllers folder which their action methods return html views.
I also use Identity and JWT for my API endpoints but:
- What if I want to use claims-based Identity in my MVC controllers which their action results return html views?
- What is the best practice for using claims-based Identity in ASP.NET Core 3.1 in such an application?
Any help would be appreciated.
After doing some research, I found the solution in ASP.NET core Authorization documentation in an article with the title "Authorize with a specific scheme in ASP.NET Core".
Based on the mentioned article in Microsoft ASP .NET core documentation, In some scenarios, such as Single Page Applications (SPAs), it's common to use multiple authentication methods. For example, the app may use cookie-based authentication to log in and JWT bearer authentication for JavaScript requests.
An authentication scheme is named when the authentication service is configured during authentication. For example:
public void ConfigureServices(IServiceCollection services)
{
// Code omitted for brevity
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the preceding code, two authentication handlers have been added: one for cookies and one for bearer.
Selecting the scheme with the Authorize attribute
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
public class MixedController : Controller
In the preceding code, only the handler with the "Bearer" scheme runs. Any cookie-based identities are ignored.
This is the solution which solved my problem and I thought it would be good to share it with you guys for those who need this.
Multiple Authentication Schemes in .Net Core 3.1 or .Net 5.0
Startup.cs
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
x.LoginPath = "/";
x.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int>("CookieExpiry"));
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetValue<string>("JWTSecret"))),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
/api/auth/login
public async Task<AuthenticationResult> Login([FromForm] string userName, [FromForm] string password, [FromHeader] string authmode = "")
{
if (userName != "demo" || password != "demo")
return new AuthenticationResult { HasError = true, Message = "Either the user name or password is incorrect." };
var claims = new Claim[]
{
new Claim(ClaimTypes.Name, userName)
};
if(authmode?.ToLower() == "token")
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_config.GetValue<string>("JWTSecret"));
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims, "JWT"),
Expires = DateTime.UtcNow.AddMinutes(_config.GetValue<int>("JWTExpiry")),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var jwt = tokenHandler.WriteToken(token);
return new AuthenticationResult { Token = jwt };
}
else
{
ClaimsPrincipal princ = new ClaimsPrincipal(new ClaimsIdentity(claims, "COOKIE"));
await HttpContext.SignInAsync(princ);
return new AuthenticationResult();
}
}
Output:
Please help me to understand the difference between JWT token validation from the ASP netcore application and the netcore Kestrel hosted application.
There are two applications that verifies token using the source code like below:
public static IServiceCollection AddJwtToken(this IServiceCollection services, OAuthConfig config)
{
services.AddMvc();
services.AddAuthorization();
Logger.DebugFormat("AddJwtBearer authority:{0} audience:{1}", config.GetAuthority(), config.Resource);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => new JwtBearerOptions
{
Authority = config.GetAuthority(),
Audience = config.Resource,
});
return services;
}
it is pretty simple and it works well if token is being validated from the asp net core 2.2 application
// in the asp.net core
var builder = WebHost.CreateDefaultBuilder(args);
builder
.UseStartup<Startup>()
.ConfigureKestrel(_ => _.ConfigureEndpoints())
.UseSerilog();
And there is another application (console) that starts the same rest service host using the UseKestrel
//in the console app
var builder = WebHost.CreateDefaultBuilder()
.UseNLog()
.UseKestrel(_ => _.ConfigureEndpoints())
.UseStartup<Startup>();
the only one significant difference is that there is UseKestrel in the console via ConfigureKestrel for asp.net core.
The same source code (and configuration) is used to get token from the Azure AD.
Please find it as the gist here.
It is configured to get token from the https://login.microsoftonline.com/{tenant}/v2.0 provider. The same token endpoint, clientid, secret and scope values are used for both cases.
The problem is that AddJwtBearer validates the token in the asp.net core and does not in the console app.
the error is
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match keys:
kid: 'BB8CeFVqyaGrGNuehJIiL4dfjzw',
token: '{"typ":"JWT","alg":"RS256","kid":"BB8CeFVqyaGrGNuehJIiL4dfjzw"}.{"aud":"2c163c99-935b-4362-ae0d-657f589f5565","iss":"https://login.microsoftonline.com/{tenantidhere}/v2.0
Why asp.net core host validates the token (for the first AddJwtBearer implementation) and console host fails?
Thank you
to solve this error I've to load keys from the openid provider as below:
Logger.DebugFormat("AddJwtBearer authority:{0} audience:{1}", config.GetAuthority(), config.Resource);
IList<string> validissuers = new List<string>()
{
config.GetAuthority(),
};
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{validissuers.Last()}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openidconfig = configManager.GetConfigurationAsync().Result;
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ =>
{
_.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
{
ValidateAudience = true,
ValidAudience = config.Resource,
ValidateIssuer = true,
ValidIssuers = new[] { config.GetAuthority() },
ValidateIssuerSigningKey = true,
IssuerSigningKeys = openidconfig.SigningKeys,
RequireExpirationTime = true,
ValidateLifetime = true,
RequireSignedTokens = true,
};
_.RequireHttpsMetadata = false;
});
And it started to work for both cases. But what is the difference with the old AddJwtBearer implementation and the new one (related to the keys validation)? Keys where downloaded and supplied using the IssuerSigningKeys = openidconfig.SigningKeys but why it is not loaded automatically using the .well-known/openid-configuration by the AddJwtBearer middleware ?
In my case, the same error was because of inadvertent use of the token received from one environment (https://dev/identity) and validated in another environment (i.e. http://local/identity).
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.