Related
I'm trying to support JWT bearer token (JSON Web Token) in my web API application and I'm getting lost.
I see support for .NET Core and for OWIN applications.
I'm currently hosting my application in IIS.
How can I achieve this authentication module in my application? Is there any way I can use the <authentication> configuration similar to the way I use forms/Windows authentication?
I answered this question: How to secure an ASP.NET Web API 4 years ago using HMAC.
Now, lots of things changed in security, especially that JWT is getting popular. In this answer, I will try to explain how to use JWT in the simplest and basic way that I can, so we won't get lost from jungle of OWIN, Oauth2, ASP.NET Identity, etc..
If you don't know about JWT tokens, you need to take a look at:
https://www.rfc-editor.org/rfc/rfc7519
Basically, a JWT token looks like this:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
A JWT token has three sections:
Header: JSON format which is encoded in Base64
Claims: JSON format which is encoded in Base64.
Signature: Created and signed based on Header and Claims which is encoded in Base64.
If you use the website jwt.io with the token above, you can decode the token and see it like below:
Technically, JWT uses a signature which is signed from headers and claims with security algorithm specified in the headers (example: HMACSHA256). Therefore, JWT must be transferred over HTTPs if you store any sensitive information in its claims.
Now, in order to use JWT authentication, you don't really need an OWIN middleware if you have a legacy Web Api system. The simple concept is how to provide JWT token and how to validate the token when the request comes. That's it.
In the demo I've created (github), to keep the JWT token lightweight, I only store username and expiration time. But this way, you have to re-build new local identity (principal) to add more information like roles, if you want to do role authorization, etc. But, if you want to add more information into JWT, it's up to you: it's very flexible.
Instead of using OWIN middleware, you can simply provide a JWT token endpoint by using a controller action:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
This is a naive action; in production you should use a POST request or a Basic Authentication endpoint to provide the JWT token.
How to generate the token based on username?
You can use the NuGet package called System.IdentityModel.Tokens.Jwt from Microsoft to generate the token, or even another package if you like. In the demo, I use HMACSHA256 with SymmetricKey:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
The endpoint to provide the JWT token is done.
How to validate the JWT when the request comes?
In the demo, I have built
JwtAuthenticationAttribute which inherits from IAuthenticationFilter (more detail about authentication filter in here).
With this attribute, you can authenticate any action: you just have to put this attribute on that action.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
You can also use OWIN middleware or DelegateHander if you want to validate all incoming requests for your WebAPI (not specific to Controller or action)
Below is the core method from authentication filter:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null || !identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
The workflow is to use the JWT library (NuGet package above) to validate the JWT token and then return back ClaimsPrincipal. You can perform more validation, like check whether user exists on your system, and add other custom validations if you want.
The code to validate JWT token and get principal back:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
If the JWT token is validated and the principal is returned, you should build a new local identity and put more information into it to check role authorization.
Remember to add config.Filters.Add(new AuthorizeAttribute()); (default authorization) at global scope in order to prevent any anonymous request to your resources.
You can use Postman to test the demo:
Request token (naive as I mentioned above, just for demo):
GET http://localhost:{port}/api/token?username=cuong&password=1
Put JWT token in the header for authorized request, example:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
The demo can be found here: https://github.com/cuongle/WebApi.Jwt
I've managed to achieve it with minimal effort (just as simple as with ASP.NET Core).
For that I use OWIN Startup.cs file and Microsoft.Owin.Security.Jwt library.
In order for the app to hit Startup.cs we need to amend Web.config:
<configuration>
<appSettings>
<add key="owin:AutomaticAppStartup" value="true" />
...
Here's how Startup.cs should look:
using MyApp.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;
[assembly: OwinStartup(typeof(MyApp.App_Start.Startup))]
namespace MyApp.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = ConfigHelper.GetAudience(),
ValidIssuer = ConfigHelper.GetIssuer(),
IssuerSigningKey = ConfigHelper.GetSymmetricSecurityKey(),
ValidateLifetime = true,
ValidateIssuerSigningKey = true
}
});
}
}
}
Many of you guys use ASP.NET Core nowadays, so as you can see it doesn't differ a lot from what we have there.
It really got me perplexed first, I was trying to implement custom providers, etc. But I didn't expect it to be so simple. OWIN just rocks!
Just one thing to mention - after I enabled OWIN Startup NSWag library stopped working for me (e.g. some of you might want to auto-generate typescript HTTP proxies for Angular app).
The solution was also very simple - I replaced NSWag with Swashbuckle and didn't have any further issues.
Ok, now sharing ConfigHelper code:
public class ConfigHelper
{
public static string GetIssuer()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Issuer"];
return result;
}
public static string GetAudience()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Audience"];
return result;
}
public static SigningCredentials GetSigningCredentials()
{
var result = new SigningCredentials(GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256);
return result;
}
public static string GetSecurityKey()
{
string result = System.Configuration.ConfigurationManager.AppSettings["SecurityKey"];
return result;
}
public static byte[] GetSymmetricSecurityKeyAsBytes()
{
var issuerSigningKey = GetSecurityKey();
byte[] data = Encoding.UTF8.GetBytes(issuerSigningKey);
return data;
}
public static SymmetricSecurityKey GetSymmetricSecurityKey()
{
byte[] data = GetSymmetricSecurityKeyAsBytes();
var result = new SymmetricSecurityKey(data);
return result;
}
public static string GetCorsOrigins()
{
string result = System.Configuration.ConfigurationManager.AppSettings["CorsOrigins"];
return result;
}
}
Another important aspect - I sent JWT Token via Authorization header, so typescript code looks for me as follows:
(the code below is generated by NSWag)
#Injectable()
export class TeamsServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(#Inject(HttpClient) http: HttpClient, #Optional() #Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "https://localhost:44384";
}
add(input: TeamDto | null): Observable<boolean> {
let url_ = this.baseUrl + "/api/Teams/Add";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + localStorage.getItem('token')
})
};
See headers part - "Authorization": "Bearer " + localStorage.getItem('token')
Here's a very minimal and secure implementation of a Claims based Authentication using JWT token in an ASP.NET Core Web API.
first of all, you need to expose an endpoint that returns a JWT token with claims assigned to a user:
/// <summary>
/// Login provides API to verify user and returns authentication token.
/// API Path: api/account/login
/// </summary>
/// <param name="paramUser">Username and Password</param>
/// <returns>{Token: [Token] }</returns>
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] UserRequestVM paramUser, CancellationToken ct)
{
var result = await UserApplication.PasswordSignInAsync(paramUser.Email, paramUser.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
UserRequestVM request = new UserRequestVM();
request.Email = paramUser.Email;
ApplicationUser UserDetails = await this.GetUserByEmail(request);
List<ApplicationClaim> UserClaims = await this.ClaimApplication.GetListByUser(UserDetails);
var Claims = new ClaimsIdentity(new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, paramUser.Email.ToString()),
new Claim(UserId, UserDetails.UserId.ToString())
});
//Adding UserClaims to JWT claims
foreach (var item in UserClaims)
{
Claims.AddClaim(new Claim(item.ClaimCode, string.Empty));
}
var tokenHandler = new JwtSecurityTokenHandler();
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
var encryptionkey = Configuration["Jwt:Encryptionkey"];
var key = Encoding.ASCII.GetBytes(encryptionkey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = Configuration["Jwt:Issuer"],
Subject = Claims,
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Configuration["Jwt:ExpiryTimeInMinutes"])),
//algorithm to sign the token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
token = tokenString
});
}
return BadRequest("Wrong Username or password");
}
now you need to Add Authentication to your services in your ConfigureServices inside your startup.cs to add JWT authentication as your default authentication service like this:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
//ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Encryptionkey"])),
ValidateAudience = false,
ValidateLifetime = true,
ValidIssuer = configuration["Jwt:Issuer"],
//ValidAudience = Configuration["Jwt:Audience"],
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"])),
};
});
now you can add policies to your authorization services like this:
services.AddAuthorization(options =>
{
options.AddPolicy("YourPolicyNameHere",
policy => policy.RequireClaim("YourClaimNameHere"));
});
ALTERNATIVELY, You can also (not necessary) populate all of your claims from your database as this will only run once on your application startup and add them to policies like this:
services.AddAuthorization(async options =>
{
var ClaimList = await claimApplication.GetList(applicationClaim);
foreach (var item in ClaimList)
{
options.AddPolicy(item.ClaimCode, policy => policy.RequireClaim(item.ClaimCode));
}
});
now you can put the Policy filter on any of the methods that you want to be authorized like this:
[HttpPost("update")]
[Authorize(Policy = "ACC_UP")]
public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
{
//your logic goes here
}
Hope this helps
In my case the JWT is created by a separate API so ASP.NET need only decode and validate it. In contrast to the accepted answer we're using RSA which is a non-symmetric algorithm, so the SymmetricSecurityKey class mentioned above won't work.
Here's the result.
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using System.Threading.Tasks;
public static async Task<JwtSecurityToken> VerifyAndDecodeJwt(string accessToken)
{
try
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{securityApiOrigin}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
RequireSignedTokens = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
};
new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var validToken);
// threw on invalid, so...
return validToken as JwtSecurityToken;
}
catch (Exception ex)
{
logger.Info(ex.Message);
return null;
}
}
I think you should use some 3d party server to support the JWT token and there is no out of the box JWT support in WEB API 2.
However there is an OWIN project for supporting some format of signed token (not JWT). It works as a reduced OAuth protocol to provide just a simple form of authentication for a web site.
You can read more about it e.g. here.
It's rather long, but most parts are details with controllers and ASP.NET Identity that you might not need at all. Most important are
Step 9: Add support for OAuth Bearer Tokens Generation
Step 12: Testing the Back-end API
There you can read how to set up endpoint (e.g. "/token") that you can access from frontend (and details on the format of the request).
Other steps provide details on how to connect that endpoint to the database, etc. and you can chose the parts that you require.
You no need to work with weird JwtSecurityTokenHandler API
Use JwtUtils Nuget package with simple API
var claims = new Dictionary<string, object>
{
{ "exp", 1639942616 },
{ "uname", "i.a.ivanov" },
{ "claim1", "claim1_value" },
{ "claims_array", new [] {"claim_item1", "claim_item2"}}
};
string token = JWT.HS256.Create(claims, "{TOKEN_SECRET}");
You can follow this code for token controller or for more details you can visit here : How to Secure API using JWT Tokens. Building CRUD API using JWT Tokens with ASP.NET Core and Entity Framework Core and Swagger
From here you can learn to use JWT Token in a very easy way
using JWTToken.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace JWTToken.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
public IConfiguration _configuration;
private readonly InventoryContext _context;
public TokenController(IConfiguration config, InventoryContext context)
{
_configuration = config;
_context = context;
}
[HttpPost]
public async Task<IActionResult> Post(UserInfo _userData)
{
if (_userData != null && _userData.Email != null && _userData.Password != null)
{
var user = await GetUser(_userData.Email, _userData.Password);
if (user != null)
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim("Id", user.UserId.ToString()),
new Claim("FirstName", user.FirstName),
new Claim("LastName", user.LastName),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_configuration["Jwt:Issuer"], _configuration["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: signIn);
return Ok(new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return BadRequest("Invalid credentials");
}
}
else
{
return BadRequest();
}
}
private async Task<UserInfo> GetUser(string email, string password)
{
return await _context.UserInfos.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
}
}
I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt packages for my .NET Core project.
There are some controller endpoints protected by the [Authorize] annotation that have to fetch the access token from the request. Currently I'm fetching the access token in my controller method this way:
string accessTokenWithBearerPrefix = Request.Headers[HeaderNames.Authorization];
string accessTokenWithoutBearerPrefix = accessTokenWithBearerPrefix.Substring("Bearer ".Length);
and I would like to know if there is a better "ready to use" solution for this because using the code above might still lead to errors while taking the substring from the bearer token.
Here is a clever way to get the header without having to go in to the headers dictionary. This will also let the framework parse the token, which is what I believe you are looking for:
[HttpGet, Route("someEndpoint")]
public IActionResult SomeEndpoint([FromHeader] string authorization)
{
if(AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
// we have a valid AuthenticationHeaderValue that has the following details:
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
// scheme will be "Bearer"
// parmameter will be the token itself.
}
return Ok();
}
You can also grab the header the old-school way:
[HttpGet, Route("someEndpoint")]
public IActionResult SomeEndpoint()
{
var authorization = Request.Headers[HeaderNames.Authorization];
if (AuthenticationHeaderValue.TryParse(authorization, out var headerValue))
{
// we have a valid AuthenticationHeaderValue that has the following details:
var scheme = headerValue.Scheme;
var parameter = headerValue.Parameter;
// scheme will be "Bearer"
// parmameter will be the token itself.
}
return Ok();
}
What's nice is AuthenticationHeaderValue.TryParse will cover oddball cases like if there is more than once space between the scheme and the token, or if there are spaces before the scheme, or spaces after the token... and trim it up for you.
Now, those cases should never happen, but... they may, and the execution of accessTokenWithBearerPrefix.Substring("Bearer ".Length); would fail. Which is why I believe you wanted a more concrete way of parsing the token.
You can set SaveToken in Startup.cs to true.
services.AddAuthentication()
.AddJwtBearer(options =>
{
// your other config
options.SaveToken = true;
});
and get access token from HttpContext with GetTokenAsync method.
using Microsoft.AspNetCore.Authentication;
public class SampleController : Controller
{
public void Index()
{
var accessToken = HttpContext.GetTokenAsync("access_token");
}
}
You can use the following code to get security token.
var stream ="[encoded jwt]";
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(stream);
var tokenS = handler.ReadToken(stream) as JwtSecurityToken;
Also, if you want to Ignore JWT Bearer token signature, you can refer to the code as below:
public TokenValidationParameters CreateTokenValidationParameters()
{
var result = new TokenValidationParameters
{
ValidateIssuer = false,
ValidIssuer = ValidIssuer,
ValidateAudience = false,
ValidAudience = ValidAudience,
ValidateIssuerSigningKey = false,
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)),
//comment this and add this line to fool the validation logic
SignatureValidator = delegate(string token, TokenValidationParameters parameters)
{
var jwt = new JwtSecurityToken(token);
return jwt;
},
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
};
result.RequireSignedTokens = false;
return result;
}
I have been trying to get JWT Authentication working and it is not entirely clear how this needs to be done, and what the best ways are to do this in ASP.NET Core 3.1.
I was using Cookie based authentication which I assume is tied to the session id, which is tied to the running server instance. If I want to use multiple servers with different IP addresses and ports, I assume that cookies would no longer work and therefor require something else that can be validated across systems.
I have been following various web examples but it is not clear what to do beyond the point where I have a JWT Token once the user has been "Authenticated" - Logged In. Once users are logged in they can access any part of the system via: html links (the menu).
How do I pass the tokens around with all subsequent requests?
Do I redirect the user to a Welcome page after the user has been authenticated and store the token in the browser sessionStore or localStorage or Cookie? What is the best way to deal with this.
options.success = function (obj) {
sessionStorage.setItem("token", obj.token);
sessionStorage.setItem("userName",$("#userName").val());
}
HTTP HEADERS
Would the Authorization HTTP Header variable work and would this be
sent around in all subsequent requests by the browser, acting as the
HTTP client. How long does this HTTP header last, is it lost once the TCP socket is closed? How do I set this HTTP Header Variable in ASP.NET Core
3.1?
Would the server then use this Header to validate the token, and also
pass it on again for use in subsequent requests?
Currently I have this, which returns the token in the body once the user is authenticated:
var claims = await GetClaims(user);
var token = GenerateSecurityToken(claims);
return Ok(new { Token = token })
AJAX CALLS
I have several forms and several AJAX calls, how do implement this as a manual approach seems rather tedious.
Is there a way to get the JWT token from a hidden form variable similar to the AntiForgery token #Html.AntiForgeryToken()
as used in all my Ajax calls?
jQuery using the hidden form variable:
request = $.ajax({
async: true,
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
headers: {
RequestVerificationToken:
$('input:hidden[name="__RequestVerificationToken"]').val()
},
WHAT DO I ADD FOR JWT ?
data: JSON.stringify(data)
}).done(function() {
completion();
}).fail(function() {
// fail
});
HTML FORMS
I have Razor Pages and have some forms which then POST back to the controllers. How do I include the token?
CONTROLLERS
Is there anything else that needs to be performed when using JWT
besides what I have in my Startup.cs? I know I need to deal with Token refreshes but I will leave for a seperate question.
LINKS FROM THE MENU - HTTP GET
I could manipulated the menu / links presented to the user, by adding the token to the end of the URL, but how should this be done?
After quite a bit of reading I found some answers along with a working solution.
HTTP HEADERS
Once you have the token, the token needs to be persisted in order to get access to the system. Using HTTP headers to store the token is not going to persist as the HTTP protocols 1.0 and 1.1 and 1.2 will close the TCP socket at some point along with the state it had, the token. Not ideal for WebClients where you do not control Http connections, but could be used for Mobile development, Android or IOS were you can control the HttpHeaders.
LOCAL STORAGE
You could use the browsers localStorage or sessionStorage, but these have some security risks where JavaScript can read the values - XSS attack.
COOKIES
Another option is to store the token within the Cookie; The cookie will be passed along with every http request, and nothing special on the client side needs to happen regarding this. This method is not prone to XSS attacks. But is prone to CSRF. But again CORS can help with this.
It is also best to set the Cookie to be HttpOnly, this way the cookie will only be delivered over HTTPS. Read more here
Here is my implementation based on an article I found
here
Startup.cs ConfigureServices...
// openssl rand -hex 16 => 32 bytes when read
var jwt_key = Configuration.GetSection("JwtOption:IssuerSigningKey").Value;
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwt_key));
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "some uri",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "the web",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
services.AddSingleton(tokenValidationParameters);
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
int minute = 60;
int hour = minute * 60;
int day = hour * 24;
int week = day * 7;
int year = 365 * day;
options.LoginPath = "/auth/login";
options.AccessDeniedPath = "/auth/accessdenied";
options.Cookie.IsEssential = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromSeconds(day/2);
options.Cookie.Name = "access_token";
options.TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters);
});
CustomJwtDataFormat This will be validating our tokens.
public class CustomJwtDataFormat :ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText)
=> Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException e)
{
System.Console.WriteLine(e);
return null;
}
catch (ArgumentException e)
{
System.Console.WriteLine(e);
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
LoginController After the username and password is validated, call SignInUser
private string GenerateSecurityToken(List<Claim> claims)
{
var tokenHandler = new JwtSecurityTokenHandler();
var expire = System.DateTime.UtcNow.AddMinutes(userService.GetJwtExpireDate());
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = expire,
SigningCredentials = new SigningCredentials(tokenValidationParameters.IssuerSigningKey, SecurityAlgorithms.HmacSha256Signature),
Audience = tokenValidationParameters.ValidAudience,
Issuer = tokenValidationParameters.ValidIssuer
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private async Task<List<Claim>> GetClaims(UserModel user) {
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Email),
new Claim(ClaimTypes.Email, user.Email),
};
// add roles
var roleList = await userService.UserRoles(user.Email);
foreach (var role in roleList)
{
var claim = new Claim(ClaimTypes.Role, role.Role);
claims.Add(claim);
}
return claims;
}
private async Task<IActionResult> SignInUser(UserModel user, bool rememberMe)
{
var claims = await GetClaims(user);
var token = GenerateSecurityToken(claims);
// return Ok(new { Token = token });
// HttpContext.Request.Headers.Add("Authorization", $"Bearer {token}");
// HttpContext.Response.Cookies.Append(
HttpContext.Response.Cookies.Append("access_token", token, new CookieOptions { HttpOnly = true, Secure = true });
return RedirectToAction("Index", "Home", new { area = "" });
}
I'm trying to support JWT bearer token (JSON Web Token) in my web API application and I'm getting lost.
I see support for .NET Core and for OWIN applications.
I'm currently hosting my application in IIS.
How can I achieve this authentication module in my application? Is there any way I can use the <authentication> configuration similar to the way I use forms/Windows authentication?
I answered this question: How to secure an ASP.NET Web API 4 years ago using HMAC.
Now, lots of things changed in security, especially that JWT is getting popular. In this answer, I will try to explain how to use JWT in the simplest and basic way that I can, so we won't get lost from jungle of OWIN, Oauth2, ASP.NET Identity, etc..
If you don't know about JWT tokens, you need to take a look at:
https://www.rfc-editor.org/rfc/rfc7519
Basically, a JWT token looks like this:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1NzI0LCJleHAiOjE0Nzc1NjY5MjQsImlhdCI6MTQ3NzU2NTcyNH0.6MzD1VwA5AcOcajkFyKhLYybr3h13iZjDyHm9zysDFQ
A JWT token has three sections:
Header: JSON format which is encoded in Base64
Claims: JSON format which is encoded in Base64.
Signature: Created and signed based on Header and Claims which is encoded in Base64.
If you use the website jwt.io with the token above, you can decode the token and see it like below:
Technically, JWT uses a signature which is signed from headers and claims with security algorithm specified in the headers (example: HMACSHA256). Therefore, JWT must be transferred over HTTPs if you store any sensitive information in its claims.
Now, in order to use JWT authentication, you don't really need an OWIN middleware if you have a legacy Web Api system. The simple concept is how to provide JWT token and how to validate the token when the request comes. That's it.
In the demo I've created (github), to keep the JWT token lightweight, I only store username and expiration time. But this way, you have to re-build new local identity (principal) to add more information like roles, if you want to do role authorization, etc. But, if you want to add more information into JWT, it's up to you: it's very flexible.
Instead of using OWIN middleware, you can simply provide a JWT token endpoint by using a controller action:
public class TokenController : ApiController
{
// This is naive endpoint for demo, it should use Basic authentication
// to provide token or POST request
[AllowAnonymous]
public string Get(string username, string password)
{
if (CheckUser(username, password))
{
return JwtManager.GenerateToken(username);
}
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
public bool CheckUser(string username, string password)
{
// should check in the database
return true;
}
}
This is a naive action; in production you should use a POST request or a Basic Authentication endpoint to provide the JWT token.
How to generate the token based on username?
You can use the NuGet package called System.IdentityModel.Tokens.Jwt from Microsoft to generate the token, or even another package if you like. In the demo, I use HMACSHA256 with SymmetricKey:
/// <summary>
/// Use the below code to generate symmetric Secret Key
/// var hmac = new HMACSHA256();
/// var key = Convert.ToBase64String(hmac.Key);
/// </summary>
private const string Secret = "db3OIsj+BXE9NZDy0t8W3TcNekrF+2d/1sFnWG4HnV8TZY30iTOdtVWJG8abWvB1GlOgJuQZdcF2Luqm/hccMw==";
public static string GenerateToken(string username, int expireMinutes = 20)
{
var symmetricKey = Convert.FromBase64String(Secret);
var tokenHandler = new JwtSecurityTokenHandler();
var now = DateTime.UtcNow;
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, username)
}),
Expires = now.AddMinutes(Convert.ToInt32(expireMinutes)),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(symmetricKey),
SecurityAlgorithms.HmacSha256Signature)
};
var stoken = tokenHandler.CreateToken(tokenDescriptor);
var token = tokenHandler.WriteToken(stoken);
return token;
}
The endpoint to provide the JWT token is done.
How to validate the JWT when the request comes?
In the demo, I have built
JwtAuthenticationAttribute which inherits from IAuthenticationFilter (more detail about authentication filter in here).
With this attribute, you can authenticate any action: you just have to put this attribute on that action.
public class ValueController : ApiController
{
[JwtAuthentication]
public string Get()
{
return "value";
}
}
You can also use OWIN middleware or DelegateHander if you want to validate all incoming requests for your WebAPI (not specific to Controller or action)
Below is the core method from authentication filter:
private static bool ValidateToken(string token, out string username)
{
username = null;
var simplePrinciple = JwtManager.GetPrincipal(token);
var identity = simplePrinciple.Identity as ClaimsIdentity;
if (identity == null || !identity.IsAuthenticated)
return false;
var usernameClaim = identity.FindFirst(ClaimTypes.Name);
username = usernameClaim?.Value;
if (string.IsNullOrEmpty(username))
return false;
// More validate to check whether username exists in system
return true;
}
protected Task<IPrincipal> AuthenticateJwtToken(string token)
{
string username;
if (ValidateToken(token, out username))
{
// based on username to get more information from database
// in order to build local identity
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, username)
// Add more claims if needed: Roles, ...
};
var identity = new ClaimsIdentity(claims, "Jwt");
IPrincipal user = new ClaimsPrincipal(identity);
return Task.FromResult(user);
}
return Task.FromResult<IPrincipal>(null);
}
The workflow is to use the JWT library (NuGet package above) to validate the JWT token and then return back ClaimsPrincipal. You can perform more validation, like check whether user exists on your system, and add other custom validations if you want.
The code to validate JWT token and get principal back:
public static ClaimsPrincipal GetPrincipal(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
if (jwtToken == null)
return null;
var symmetricKey = Convert.FromBase64String(Secret);
var validationParameters = new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
return principal;
}
catch (Exception)
{
//should write log
return null;
}
}
If the JWT token is validated and the principal is returned, you should build a new local identity and put more information into it to check role authorization.
Remember to add config.Filters.Add(new AuthorizeAttribute()); (default authorization) at global scope in order to prevent any anonymous request to your resources.
You can use Postman to test the demo:
Request token (naive as I mentioned above, just for demo):
GET http://localhost:{port}/api/token?username=cuong&password=1
Put JWT token in the header for authorized request, example:
GET http://localhost:{port}/api/value
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImN1b25nIiwibmJmIjoxNDc3NTY1MjU4LCJleHAiOjE0Nzc1NjY0NTgsImlhdCI6MTQ3NzU2NTI1OH0.dSwwufd4-gztkLpttZsZ1255oEzpWCJkayR_4yvNL1s
The demo can be found here: https://github.com/cuongle/WebApi.Jwt
I've managed to achieve it with minimal effort (just as simple as with ASP.NET Core).
For that I use OWIN Startup.cs file and Microsoft.Owin.Security.Jwt library.
In order for the app to hit Startup.cs we need to amend Web.config:
<configuration>
<appSettings>
<add key="owin:AutomaticAppStartup" value="true" />
...
Here's how Startup.cs should look:
using MyApp.Helpers;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Jwt;
using Owin;
[assembly: OwinStartup(typeof(MyApp.App_Start.Startup))]
namespace MyApp.App_Start
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidAudience = ConfigHelper.GetAudience(),
ValidIssuer = ConfigHelper.GetIssuer(),
IssuerSigningKey = ConfigHelper.GetSymmetricSecurityKey(),
ValidateLifetime = true,
ValidateIssuerSigningKey = true
}
});
}
}
}
Many of you guys use ASP.NET Core nowadays, so as you can see it doesn't differ a lot from what we have there.
It really got me perplexed first, I was trying to implement custom providers, etc. But I didn't expect it to be so simple. OWIN just rocks!
Just one thing to mention - after I enabled OWIN Startup NSWag library stopped working for me (e.g. some of you might want to auto-generate typescript HTTP proxies for Angular app).
The solution was also very simple - I replaced NSWag with Swashbuckle and didn't have any further issues.
Ok, now sharing ConfigHelper code:
public class ConfigHelper
{
public static string GetIssuer()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Issuer"];
return result;
}
public static string GetAudience()
{
string result = System.Configuration.ConfigurationManager.AppSettings["Audience"];
return result;
}
public static SigningCredentials GetSigningCredentials()
{
var result = new SigningCredentials(GetSymmetricSecurityKey(), SecurityAlgorithms.HmacSha256);
return result;
}
public static string GetSecurityKey()
{
string result = System.Configuration.ConfigurationManager.AppSettings["SecurityKey"];
return result;
}
public static byte[] GetSymmetricSecurityKeyAsBytes()
{
var issuerSigningKey = GetSecurityKey();
byte[] data = Encoding.UTF8.GetBytes(issuerSigningKey);
return data;
}
public static SymmetricSecurityKey GetSymmetricSecurityKey()
{
byte[] data = GetSymmetricSecurityKeyAsBytes();
var result = new SymmetricSecurityKey(data);
return result;
}
public static string GetCorsOrigins()
{
string result = System.Configuration.ConfigurationManager.AppSettings["CorsOrigins"];
return result;
}
}
Another important aspect - I sent JWT Token via Authorization header, so typescript code looks for me as follows:
(the code below is generated by NSWag)
#Injectable()
export class TeamsServiceProxy {
private http: HttpClient;
private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;
constructor(#Inject(HttpClient) http: HttpClient, #Optional() #Inject(API_BASE_URL) baseUrl?: string) {
this.http = http;
this.baseUrl = baseUrl ? baseUrl : "https://localhost:44384";
}
add(input: TeamDto | null): Observable<boolean> {
let url_ = this.baseUrl + "/api/Teams/Add";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(input);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": "Bearer " + localStorage.getItem('token')
})
};
See headers part - "Authorization": "Bearer " + localStorage.getItem('token')
Here's a very minimal and secure implementation of a Claims based Authentication using JWT token in an ASP.NET Core Web API.
first of all, you need to expose an endpoint that returns a JWT token with claims assigned to a user:
/// <summary>
/// Login provides API to verify user and returns authentication token.
/// API Path: api/account/login
/// </summary>
/// <param name="paramUser">Username and Password</param>
/// <returns>{Token: [Token] }</returns>
[HttpPost("login")]
[AllowAnonymous]
public async Task<IActionResult> Login([FromBody] UserRequestVM paramUser, CancellationToken ct)
{
var result = await UserApplication.PasswordSignInAsync(paramUser.Email, paramUser.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
UserRequestVM request = new UserRequestVM();
request.Email = paramUser.Email;
ApplicationUser UserDetails = await this.GetUserByEmail(request);
List<ApplicationClaim> UserClaims = await this.ClaimApplication.GetListByUser(UserDetails);
var Claims = new ClaimsIdentity(new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, paramUser.Email.ToString()),
new Claim(UserId, UserDetails.UserId.ToString())
});
//Adding UserClaims to JWT claims
foreach (var item in UserClaims)
{
Claims.AddClaim(new Claim(item.ClaimCode, string.Empty));
}
var tokenHandler = new JwtSecurityTokenHandler();
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
var encryptionkey = Configuration["Jwt:Encryptionkey"];
var key = Encoding.ASCII.GetBytes(encryptionkey);
var tokenDescriptor = new SecurityTokenDescriptor
{
Issuer = Configuration["Jwt:Issuer"],
Subject = Claims,
// this information will be retrived from you Configuration
//I have injected Configuration provider service into my controller
Expires = DateTime.UtcNow.AddMinutes(Convert.ToDouble(Configuration["Jwt:ExpiryTimeInMinutes"])),
//algorithm to sign the token
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
return Ok(new
{
token = tokenString
});
}
return BadRequest("Wrong Username or password");
}
now you need to Add Authentication to your services in your ConfigureServices inside your startup.cs to add JWT authentication as your default authentication service like this:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
//ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Encryptionkey"])),
ValidateAudience = false,
ValidateLifetime = true,
ValidIssuer = configuration["Jwt:Issuer"],
//ValidAudience = Configuration["Jwt:Audience"],
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:Key"])),
};
});
now you can add policies to your authorization services like this:
services.AddAuthorization(options =>
{
options.AddPolicy("YourPolicyNameHere",
policy => policy.RequireClaim("YourClaimNameHere"));
});
ALTERNATIVELY, You can also (not necessary) populate all of your claims from your database as this will only run once on your application startup and add them to policies like this:
services.AddAuthorization(async options =>
{
var ClaimList = await claimApplication.GetList(applicationClaim);
foreach (var item in ClaimList)
{
options.AddPolicy(item.ClaimCode, policy => policy.RequireClaim(item.ClaimCode));
}
});
now you can put the Policy filter on any of the methods that you want to be authorized like this:
[HttpPost("update")]
[Authorize(Policy = "ACC_UP")]
public async Task<IActionResult> Update([FromBody] UserRequestVM requestVm, CancellationToken ct)
{
//your logic goes here
}
Hope this helps
In my case the JWT is created by a separate API so ASP.NET need only decode and validate it. In contrast to the accepted answer we're using RSA which is a non-symmetric algorithm, so the SymmetricSecurityKey class mentioned above won't work.
Here's the result.
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using System.Threading.Tasks;
public static async Task<JwtSecurityToken> VerifyAndDecodeJwt(string accessToken)
{
try
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{securityApiOrigin}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
RequireSignedTokens = true,
IssuerSigningKeys = openIdConfig.SigningKeys,
};
new JwtSecurityTokenHandler().ValidateToken(accessToken, validationParameters, out var validToken);
// threw on invalid, so...
return validToken as JwtSecurityToken;
}
catch (Exception ex)
{
logger.Info(ex.Message);
return null;
}
}
I think you should use some 3d party server to support the JWT token and there is no out of the box JWT support in WEB API 2.
However there is an OWIN project for supporting some format of signed token (not JWT). It works as a reduced OAuth protocol to provide just a simple form of authentication for a web site.
You can read more about it e.g. here.
It's rather long, but most parts are details with controllers and ASP.NET Identity that you might not need at all. Most important are
Step 9: Add support for OAuth Bearer Tokens Generation
Step 12: Testing the Back-end API
There you can read how to set up endpoint (e.g. "/token") that you can access from frontend (and details on the format of the request).
Other steps provide details on how to connect that endpoint to the database, etc. and you can chose the parts that you require.
You no need to work with weird JwtSecurityTokenHandler API
Use JwtUtils Nuget package with simple API
var claims = new Dictionary<string, object>
{
{ "exp", 1639942616 },
{ "uname", "i.a.ivanov" },
{ "claim1", "claim1_value" },
{ "claims_array", new [] {"claim_item1", "claim_item2"}}
};
string token = JWT.HS256.Create(claims, "{TOKEN_SECRET}");
You can follow this code for token controller or for more details you can visit here : How to Secure API using JWT Tokens. Building CRUD API using JWT Tokens with ASP.NET Core and Entity Framework Core and Swagger
From here you can learn to use JWT Token in a very easy way
using JWTToken.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace JWTToken.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class TokenController : ControllerBase
{
public IConfiguration _configuration;
private readonly InventoryContext _context;
public TokenController(IConfiguration config, InventoryContext context)
{
_configuration = config;
_context = context;
}
[HttpPost]
public async Task<IActionResult> Post(UserInfo _userData)
{
if (_userData != null && _userData.Email != null && _userData.Password != null)
{
var user = await GetUser(_userData.Email, _userData.Password);
if (user != null)
{
//create claims details based on the user information
var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
new Claim("Id", user.UserId.ToString()),
new Claim("FirstName", user.FirstName),
new Claim("LastName", user.LastName),
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]));
var signIn = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(_configuration["Jwt:Issuer"], _configuration["Jwt:Audience"], claims, expires: DateTime.UtcNow.AddDays(1), signingCredentials: signIn);
return Ok(new JwtSecurityTokenHandler().WriteToken(token));
}
else
{
return BadRequest("Invalid credentials");
}
}
else
{
return BadRequest();
}
}
private async Task<UserInfo> GetUser(string email, string password)
{
return await _context.UserInfos.FirstOrDefaultAsync(u => u.Email == email && u.Password == password);
}
}
}
I'm looking for the simplest way to setup a Web API server that uses JWTs for authentication in ASP.NET Core (aka ASP.NET 5). This project (blog post / github) does exactly what I'm looking for but it uses ASP.NET 4.
I just want to be able to:
setup a login route that can create a JWT token and return it in the header. I'm integrating this with an existing RESTful service that will tell me if the username and password are valid. In the ASP.NET 4 project I'm looking at this could be done with the following route https://github.com/stewartm83/Jwt-WebApi/blob/master/src/JwtWebApi/Controllers/AccountController.cs#L24-L54
Intercept incoming requests to routes that require authorization, decrypt and validate the JWT token coming in the header and make the user information in the JWT token's payload accessible to the route. e.g. something like this: https://github.com/stewartm83/Jwt-WebApi/blob/master/src/JwtWebApi/App_Start/AuthHandler.cs
All of the examples I've seen in ASP.NET Core are very complex and rely on some or all of OAuth, IS, OpenIddict, and EF which I would like to avoid.
Can anyone point me towards an example of how to do this in ASP.NET Core or help me get started with this?
EDIT: Answer
I ended up using this answer: https://stackoverflow.com/a/33217340/373655
Note/Update:
The below code was for .NET Core 1.1
Since .NET Core 1 was so very RTM, authentication changed with the jump from .NET Core 1 to 2.0 (aka was [partially?] fixed with breaking changes).
That's why the bellow code does NOT work with .NET Core 2.0 anymore.
But it will still be a useful read.
2018 Update
Meanwhile, you can find a working example of ASP.NET Core 2.0 JWT-Cookie-Authentication on my github test repo.
Comes complete with an implementation of the MS-RSA&MS-ECDSA abstract class with BouncyCastle, and a key-generator for RSA&ECDSA.
Necromancing.
I digged deeper into JWT. Here are my findings:
You need to add Microsoft.AspNetCore.Authentication.JwtBearer
then you can set
app.UseJwtBearerAuthentication(bearerOptions);
in Startup.cs => Configure
where bearerOptions is defined by you, e.g. as
var bearerOptions = new JwtBearerOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters,
Events = new CustomBearerEvents()
};
// Optional
// bearerOptions.SecurityTokenValidators.Clear();
// bearerOptions.SecurityTokenValidators.Add(new MyTokenHandler());
where CustomBearerEvents is the place where you could add token data to the httpContext/Route
// https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerEvents.cs
public class CustomBearerEvents : Microsoft.AspNetCore.Authentication.JwtBearer.IJwtBearerEvents
{
/// <summary>
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.
/// </summary>
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked when a protocol message is first received.
/// </summary>
public Func<MessageReceivedContext, Task> OnMessageReceived { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated.
/// </summary>
public Func<TokenValidatedContext, Task> OnTokenValidated { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked before a challenge is sent back to the caller.
/// </summary>
public Func<JwtBearerChallengeContext, Task> OnChallenge { get; set; } = context => Task.FromResult(0);
Task IJwtBearerEvents.AuthenticationFailed(AuthenticationFailedContext context)
{
return OnAuthenticationFailed(context);
}
Task IJwtBearerEvents.Challenge(JwtBearerChallengeContext context)
{
return OnChallenge(context);
}
Task IJwtBearerEvents.MessageReceived(MessageReceivedContext context)
{
return OnMessageReceived(context);
}
Task IJwtBearerEvents.TokenValidated(TokenValidatedContext context)
{
return OnTokenValidated(context);
}
}
And tokenValidationParameters is defined by you, e.g.
var tokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = "ExampleIssuer",
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = "ExampleAudience",
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero,
};
And MyTokenHandler is optionally defined by you, if you want to customize token validation, e.g.
// https://gist.github.com/pmhsfelix/4151369
public class MyTokenHandler : Microsoft.IdentityModel.Tokens.ISecurityTokenValidator
{
private int m_MaximumTokenByteSize;
public MyTokenHandler()
{ }
bool ISecurityTokenValidator.CanValidateToken
{
get
{
// throw new NotImplementedException();
return true;
}
}
int ISecurityTokenValidator.MaximumTokenSizeInBytes
{
get
{
return this.m_MaximumTokenByteSize;
}
set
{
this.m_MaximumTokenByteSize = value;
}
}
bool ISecurityTokenValidator.CanReadToken(string securityToken)
{
System.Console.WriteLine(securityToken);
return true;
}
ClaimsPrincipal ISecurityTokenValidator.ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
// validatedToken = new JwtSecurityToken(securityToken);
try
{
tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);
validatedToken = new JwtSecurityToken("jwtEncodedString");
}
catch (Exception ex)
{
System.Console.WriteLine(ex.Message);
throw;
}
ClaimsPrincipal principal = null;
// SecurityToken validToken = null;
validatedToken = null;
System.Collections.Generic.List<System.Security.Claims.Claim> ls =
new System.Collections.Generic.List<System.Security.Claims.Claim>();
ls.Add(
new System.Security.Claims.Claim(
System.Security.Claims.ClaimTypes.Name, "IcanHazUsr_éèêëïàáâäåãæóòôöõõúùûüñçø_ÉÈÊËÏÀÁÂÄÅÃÆÓÒÔÖÕÕÚÙÛÜÑÇØ 你好,世界 Привет\tмир"
, System.Security.Claims.ClaimValueTypes.String
)
);
//
System.Security.Claims.ClaimsIdentity id = new System.Security.Claims.ClaimsIdentity("authenticationType");
id.AddClaims(ls);
principal = new System.Security.Claims.ClaimsPrincipal(id);
return principal;
throw new NotImplementedException();
}
}
The tricky part is how to get the AsymmetricSecurityKey, because you don't want to pass a rsaCryptoServiceProvider, because you need interoperability in crypto format.
Creation goes along the lines of
// System.Security.Cryptography.X509Certificates.X509Certificate2 cert2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(byte[] rawData);
System.Security.Cryptography.X509Certificates.X509Certificate2 cert2 =
DotNetUtilities.CreateX509Cert2("mycert");
Microsoft.IdentityModel.Tokens.SecurityKey secKey = new X509SecurityKey(cert2);
e.g. with BouncyCastle from a DER Certificate:
// http://stackoverflow.com/questions/36942094/how-can-i-generate-a-self-signed-cert-without-using-obsolete-bouncycastle-1-7-0
public static System.Security.Cryptography.X509Certificates.X509Certificate2 CreateX509Cert2(string certName)
{
var keypairgen = new Org.BouncyCastle.Crypto.Generators.RsaKeyPairGenerator();
keypairgen.Init(new Org.BouncyCastle.Crypto.KeyGenerationParameters(
new Org.BouncyCastle.Security.SecureRandom(
new Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator()
)
, 1024
)
);
Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keypair = keypairgen.GenerateKeyPair();
// --- Until here we generate a keypair
var random = new Org.BouncyCastle.Security.SecureRandom(
new Org.BouncyCastle.Crypto.Prng.CryptoApiRandomGenerator()
);
// SHA1WITHRSA
// SHA256WITHRSA
// SHA384WITHRSA
// SHA512WITHRSA
// SHA1WITHECDSA
// SHA224WITHECDSA
// SHA256WITHECDSA
// SHA384WITHECDSA
// SHA512WITHECDSA
Org.BouncyCastle.Crypto.ISignatureFactory signatureFactory =
new Org.BouncyCastle.Crypto.Operators.Asn1SignatureFactory("SHA512WITHRSA", keypair.Private, random)
;
var gen = new Org.BouncyCastle.X509.X509V3CertificateGenerator();
var CN = new Org.BouncyCastle.Asn1.X509.X509Name("CN=" + certName);
var SN = Org.BouncyCastle.Math.BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(SN);
gen.SetSubjectDN(CN);
gen.SetIssuerDN(CN);
gen.SetNotAfter(DateTime.Now.AddYears(1));
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
gen.SetPublicKey(keypair.Public);
// -- Are these necessary ?
// public static readonly DerObjectIdentifier AuthorityKeyIdentifier = new DerObjectIdentifier("2.5.29.35");
// OID value: 2.5.29.35
// OID description: id-ce-authorityKeyIdentifier
// This extension may be used either as a certificate or CRL extension.
// It identifies the public key to be used to verify the signature on this certificate or CRL.
// It enables distinct keys used by the same CA to be distinguished (e.g., as key updating occurs).
// http://stackoverflow.com/questions/14930381/generating-x509-certificate-using-bouncy-castle-java
gen.AddExtension(
Org.BouncyCastle.Asn1.X509.X509Extensions.AuthorityKeyIdentifier.Id,
false,
new Org.BouncyCastle.Asn1.X509.AuthorityKeyIdentifier(
Org.BouncyCastle.X509.SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keypair.Public),
new Org.BouncyCastle.Asn1.X509.GeneralNames(new Org.BouncyCastle.Asn1.X509.GeneralName(CN)),
SN
));
// OID value: 1.3.6.1.5.5.7.3.1
// OID description: Indicates that a certificate can be used as an SSL server certificate.
gen.AddExtension(
Org.BouncyCastle.Asn1.X509.X509Extensions.ExtendedKeyUsage.Id,
false,
new Org.BouncyCastle.Asn1.X509.ExtendedKeyUsage(new ArrayList()
{
new Org.BouncyCastle.Asn1.DerObjectIdentifier("1.3.6.1.5.5.7.3.1")
}));
// -- End are these necessary ?
Org.BouncyCastle.X509.X509Certificate bouncyCert = gen.Generate(signatureFactory);
byte[] ba = bouncyCert.GetEncoded();
System.Security.Cryptography.X509Certificates.X509Certificate2 msCert = new System.Security.Cryptography.X509Certificates.X509Certificate2(ba);
return msCert;
}
Subsequently, you can add a custom cookie-format that contains the JWT-Bearer:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationScheme = "MyCookieMiddlewareInstance",
CookieName = "SecurityByObscurityDoesntWork",
ExpireTimeSpan = new System.TimeSpan(15, 0, 0),
LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Unauthorized/"),
AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Forbidden/"),
AutomaticAuthenticate = true,
AutomaticChallenge = true,
CookieSecure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest,
CookieHttpOnly = false,
TicketDataFormat = new CustomJwtDataFormat("foo", tokenValidationParameters)
// DataProtectionProvider = null,
// DataProtectionProvider = new DataProtectionProvider(new System.IO.DirectoryInfo(#"c:\shared-auth-ticket-keys\"),
//delegate (DataProtectionConfiguration options)
//{
// var op = new Microsoft.AspNet.DataProtection.AuthenticatedEncryption.AuthenticatedEncryptionOptions();
// op.EncryptionAlgorithm = Microsoft.AspNet.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_GCM:
// options.UseCryptographicAlgorithms(op);
//}
//),
});
Where CustomJwtDataFormat is something along the lines of
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
// This ISecureDataFormat implementation is decode-only
string ISecureDataFormat<AuthenticationTicket>.Protect(AuthenticationTicket data)
{
return MyProtect(data, null);
}
string ISecureDataFormat<AuthenticationTicket>.Protect(AuthenticationTicket data, string purpose)
{
return MyProtect(data, purpose);
}
AuthenticationTicket ISecureDataFormat<AuthenticationTicket>.Unprotect(string protectedText)
{
return MyUnprotect(protectedText, null);
}
AuthenticationTicket ISecureDataFormat<AuthenticationTicket>.Unprotect(string protectedText, string purpose)
{
return MyUnprotect(protectedText, purpose);
}
private string MyProtect(AuthenticationTicket data, string purpose)
{
return "wadehadedudada";
throw new System.NotImplementedException();
}
// http://blogs.microsoft.co.il/sasha/2012/01/20/aggressive-inlining-in-the-clr-45-jit/
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
private AuthenticationTicket MyUnprotect(string protectedText, string purpose)
{
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
System.Collections.Generic.List<System.Security.Claims.Claim> ls =
new System.Collections.Generic.List<System.Security.Claims.Claim>();
ls.Add(
new System.Security.Claims.Claim(
System.Security.Claims.ClaimTypes.Name, "IcanHazUsr_éèêëïàáâäåãæóòôöõõúùûüñçø_ÉÈÊËÏÀÁÂÄÅÃÆÓÒÔÖÕÕÚÙÛÜÑÇØ 你好,世界 Привет\tмир"
, System.Security.Claims.ClaimValueTypes.String
)
);
//
System.Security.Claims.ClaimsIdentity id = new System.Security.Claims.ClaimsIdentity("authenticationType");
id.AddClaims(ls);
principal = new System.Security.Claims.ClaimsPrincipal(id);
return new AuthenticationTicket(principal, new AuthenticationProperties(), "MyCookieMiddlewareInstance");
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
JwtSecurityToken validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new System.ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, System.StringComparison.Ordinal))
{
throw new System.ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (System.ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new AuthenticationProperties(), "MyCookieMiddlewareInstance");
}
}
And you can also create the JWT-token with Microsoft.IdentityModel.Token:
// https://github.com/aspnet/Security/blob/master/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/IJwtBearerEvents.cs
// http://codereview.stackexchange.com/questions/45974/web-api-2-authentication-with-jwt
public class TokenMaker
{
class SecurityConstants
{
public static string TokenIssuer;
public static string TokenAudience;
public static int TokenLifetimeMinutes;
}
public static string IssueToken()
{
SecurityKey sSKey = null;
var claimList = new List<Claim>()
{
new Claim(ClaimTypes.Name, "userName"),
new Claim(ClaimTypes.Role, "role") //Not sure what this is for
};
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
SecurityTokenDescriptor desc = makeSecurityTokenDescriptor(sSKey, claimList);
// JwtSecurityToken tok = tokenHandler.CreateJwtSecurityToken(desc);
return tokenHandler.CreateEncodedJwt(desc);
}
public static ClaimsPrincipal ValidateJwtToken(string jwtToken)
{
SecurityKey sSKey = null;
var tokenHandler = new JwtSecurityTokenHandler();
// Parse JWT from the Base64UrlEncoded wire form
//(<Base64UrlEncoded header>.<Base64UrlEncoded body>.<signature>)
JwtSecurityToken parsedJwt = tokenHandler.ReadToken(jwtToken) as JwtSecurityToken;
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
RequireExpirationTime = true,
ValidAudience = SecurityConstants.TokenAudience,
ValidIssuers = new List<string>() { SecurityConstants.TokenIssuer },
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKey = sSKey,
};
SecurityToken secT;
return tokenHandler.ValidateToken("token", validationParams, out secT);
}
private static SecurityTokenDescriptor makeSecurityTokenDescriptor(SecurityKey sSKey, List<Claim> claimList)
{
var now = DateTime.UtcNow;
Claim[] claims = claimList.ToArray();
return new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Issuer = SecurityConstants.TokenIssuer,
Audience = SecurityConstants.TokenAudience,
IssuedAt = System.DateTime.UtcNow,
Expires = System.DateTime.UtcNow.AddMinutes(SecurityConstants.TokenLifetimeMinutes),
NotBefore = System.DateTime.UtcNow.AddTicks(-1),
SigningCredentials = new SigningCredentials(sSKey, Microsoft.IdentityModel.Tokens.SecurityAlgorithms.EcdsaSha512Signature)
};
}
}
Note that because you can give a different user in the cookie vs. http-headers (Bearer), or any other authentication method that you specify, you can actually have MORE than 1 user !
Take a look at this:
https://stormpath.com/blog/token-authentication-asp-net-core
it should be exactly what you're looking for.
There's also these two:
https://goblincoding.com/2016/07/03/issuing-and-authenticating-jwt-tokens-in-asp-net-core-webapi-part-i/
https://goblincoding.com/2016/07/07/issuing-and-authenticating-jwt-tokens-in-asp-net-core-webapi-part-ii/
and this one
http://blog.novanet.no/hooking-up-asp-net-core-1-rc1-web-api-with-auth0-bearer-tokens/
And the JWT-Bearer sources
https://github.com/aspnet/Security/tree/master/src/Microsoft.AspNetCore.Authentication.JwtBearer
If you need ultra-high security, you should protect against replay-attacks by renewing the ticket on each request, and invalidate old tickets after a certain timeout, and after user logout (not just after validity expiration).
For those of you who end up from here via google, you can implement a TicketDataFormat in cookie-authentication when you want to use your own version of JWT.
I had to look into JWT for work, because we needed to secure our application.
Because I still had to use .NET 2.0, I had to write my own library.
I've ported the result of that to .NET Core this weekend.
You find it here:
https://github.com/ststeiger/Jwt_Net20/tree/master/CoreJWT
It doesn't use any database, that's not the job of a JWT libary.
Getting and setting DB data is your job.
The library allows for JWT authorization and verification in .NET Core with all algorithms specified in the JWT RFC listed on the IANA JOSE assignment.
As for adding authorization to the pipeline and adding values to route - these are two things which should be done separately, and I think you best do that yourselfs.
You can use custom authentication in ASP.NET Core.
Look into the "Security" category of docs on docs.asp.net.
Or you can look into the Cookie Middleware without ASP.NET Identity or into Custom Policy-Based Authorization.
You can also learn more in the auth workshop on github or in the social login section or in this channel 9 video tutorial.
If all else fails, the source code of asp.net security is on github.
The original project for .NET 3.5, which is where my library derives from, is here:
https://github.com/jwt-dotnet/jwt
I removed all references to LINQ + extension methods, because they are not supported in .NET 2.0. If you include either LINQ, or ExtensionAttribute in the sourcecode, then you can't just change the .NET runtime without getting warnings; that's why I have completely removed them.
Also, I've added RSA + ECDSA JWS-methods, for that reason the CoreJWT-project depends on BouncyCastle.
If you limit yourselfs to HMAC-SHA256 + HMAC-SHA384 + HMAC-SHA512, you can remove BouncyCastle.
JWE is not (yet) supported.
Usage is just like jwt-dotnet/jwt, except I changed the namespace JWT to CoreJWT.
I also added an internal copy of PetaJSON as serializer, so there is no interference with other people's project's dependencies.
Create a JWT-token:
var payload = new Dictionary<string, object>()
{
{ "claim1", 0 },
{ "claim2", "claim2-value" }
};
var secretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
string token = JWT.JsonWebToken.Encode(payload, secretKey, JWT.JwtHashAlgorithm.HS256);
Console.WriteLine(token);
Verify a JWT-token:
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s";
var secretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
try
{
string jsonPayload = JWT.JsonWebToken.Decode(token, secretKey);
Console.WriteLine(jsonPayload);
}
catch (JWT.SignatureVerificationException)
{
Console.WriteLine("Invalid token!");
}
For RSA & ECSA, you'll have to pass the (BouncyCastle) RSA/ECDSA private key instead of secretKey.
namespace BouncyJWT
{
public class JwtKey
{
public byte[] MacKeyBytes;
public Org.BouncyCastle.Crypto.AsymmetricKeyParameter RsaPrivateKey;
public Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters EcPrivateKey;
public string MacKey
{
get { return System.Text.Encoding.UTF8.GetString(this.MacKeyBytes); }
set { this.MacKeyBytes = System.Text.Encoding.UTF8.GetBytes(value); }
}
public JwtKey()
{ }
public JwtKey(string macKey)
{
this.MacKey = macKey;
}
public JwtKey(byte[] macKey)
{
this.MacKeyBytes = macKey;
}
public JwtKey(Org.BouncyCastle.Crypto.AsymmetricKeyParameter rsaPrivateKey)
{
this.RsaPrivateKey = rsaPrivateKey;
}
public JwtKey(Org.BouncyCastle.Crypto.Parameters.ECPrivateKeyParameters ecPrivateKey)
{
this.EcPrivateKey = ecPrivateKey;
}
}
}
For how to generate/export/import RSA/ECDSA-keys with BouncyCastle, see the project called "BouncyCastleTests" in the same repository. I leave it to you to safely store and retrieve your own RSA/ECDSA private keys.
I've verified my library's results for HMAC-ShaXXX and RSA-XXX with JWT.io - it looks like they are OK.
ECSD should be OK, too, but I didn't test it against anything.
I did not run extensive tests anyway, FYI.
The easiest option I have found so far is OpenIddict. You say you want to avoid Entity Framework and OpenIddict - then you'll be doing a lot of coding yourself, effectively rewriting parts of OpenIddict and ASOS (which OpenIddict uses) to do what they are doing anyway.
If you're ok using OpenIddict, this is practically all the configuration you will need, below. It's very simple.
If you don't want to use EF, that's possible with OpenIddict. I'm not sure how but that's the bit you'll have to figure out.
ConfigureServices:
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddOpenIddictCore<Application>(config => config.UseEntityFramework()); // this line is for OpenIddict
Configure
app.UseOpenIddictCore(builder =>
{
// tell openiddict you're wanting to use jwt tokens
builder.Options.UseJwtTokens();
// NOTE: for dev consumption only! for live, this is not encouraged!
builder.Options.AllowInsecureHttp = true;
builder.Options.ApplicationCanDisplayErrors = true;
});
// use jwt bearer authentication
app.UseJwtBearerAuthentication(options =>
{
options.AutomaticAuthenticate = true;
options.AutomaticChallenge = true;
options.RequireHttpsMetadata = false;
// these urls must match the value sent in the payload posted from the client side during login
options.Audience = "http://localhost:58292/";
options.Authority = "http://localhost:58292/";
});
There are one or two other minor things, such as your DbContext needs to derive from OpenIddictContext<ApplicationUser, Application, ApplicationRole, string>.
You can see a full length explanation (including links to the github repo) on this blog post:
http://capesean.co.za/blog/asp-net-5-jwt-tokens/
If all you need is authentication against an external OAuth/OpenID provider (such as Google, GitHub, Facebook, Microsoft Account etc.), then you don't need any third party tools.
Authentication providers for the most commonly used OAuth and OpenID providers are already provided with ASP.NET Core in the Microsoft.AspNetCore.Authorization.* packages. Check out the samples provided on the GitHub repository of the "Security" repository
If you need to create your own JWT tokens, then you need an OAuth/OpenID server. OpenIddict is an easy to setup authorization server. For this you need some form of database, as external providers will be used to authenticate the person, but you also need them to have an account on your authorization server.
If you need more customization and more control of the flow, you got to use ASOS or IdentityServer4 (currently only supported on ASP.NET Core when working against full .NET Framework or Mono. Core runtime isn't supported yet as far as I know.
There is also a Gitter Chatroom for OpenIddict at https://gitter.im/openiddict/core and https://gitter.im/aspnet-contrib/AspNet.Security.OpenIdConnect.Server for ASOS.
There is a full example for ASP.NET Core + JWT Auth + SQL Server + Swagger: https://github.com/wilsonwu/netcoreauth
Hope this can help you.
Securing ASP.NET Core 2.0 Web API with Standard JWT Bearer Token based authentication
https://fullstackmark.com/post/13/jwt-authentication-with-aspnet-core-2-web-api-angular-5-net-core-identity-and-facebook-login
& apply the authorize filter as follows
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Here is a package which
Makes integrating JWT Bearer Token Security in your Asp Net Core 2.0+ app a breeze!
Azure Active Directory auth integration.
Facebook auth integration.
Also, Swagger UI integration!
It is called AspNetCore.Security.Jwt
GitHub:
https://github.com/VeritasSoftware/AspNetCore.Security.Jwt
The package integrates JWT bearer token into your app as below:
1. Implement IAuthentication interface in your app
using AspNetCore.Security.Jwt;
using System.Threading.Tasks;
namespace XXX.API
{
public class Authenticator : IAuthentication
{
public async Task<bool> IsValidUser(string id, string password)
{
//Put your id authenication here.
return true;
}
}
}
2. In your Startup.cs
using AspNetCore.Security.Jwt;
using Swashbuckle.AspNetCore.Swagger;
.
.
public void ConfigureServices(IServiceCollection services)
{
.
.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "XXX API", Version = "v1" });
});
services.AddSecurity<Authenticator>(this.Configuration, true);
services.AddMvc().AddSecurity();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
.
.
.
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "XXX API V1");
});
app.UseSecurity(true);
app.UseMvc();
}
3. In your appsettings.json
Note:- You can put these settings in Secret Manager by using Manage User Secrets menu (right-click your Project).
{
"SecuritySettings": {
"Secret": "a secret that needs to be at least 16 characters long",
"Issuer": "your app",
"Audience": "the client of your app",
"IdType": "Name",
"TokenExpiryInHours" : 2
},
.
.
.
}
Then you will get endpoints automatically:
/token
/facebook
When you call these endpoints and are successfully authenticated, you will get back a JWT Bearer Token.
In your Controller that you want to secure
You must mark the Controller or Action that you want to secure with Authorize attribute like:
using Microsoft.AspNetCore.Mvc;
.
.
.
namespace XXX.API.Controllers
{
using Microsoft.AspNetCore.Authorization;
[Authorize]
[Route("api/[controller]")]
public class XXXController : Controller
{
.
.
.
}
}
In Swagger UI, you will automatically see these endpoints.