Please review my code for bearer token (JWT) authentication of Web API 2 (Self Hosted using OWIN)
Are there any security issues in the implementation?
Quick overview:
- Token creation and validation using JWT Handler
- Symmetric key encryption
- CORS support not yet checked for the authorization header
- Web traffic will be on SSL.
- The key cannot be auto-generated as it will break during a load balanced scenario. Can I save the key in config? Or switch to X509 certificates?
This is the main class to create and validate tokens:
public class TokenManager
{
public static string CreateJwtToken(string userName, string role)
{
var claimList = new List<Claim>()
{
new Claim(ClaimTypes.Name, userName),
new Claim(ClaimTypes.Role, role) //Not sure what this is for
};
var tokenHandler = new JwtSecurityTokenHandler() { RequireExpirationTime = true };
var sSKey = new InMemorySymmetricSecurityKey(SecurityConstants.KeyForHmacSha256);
var jwtToken = tokenHandler.CreateToken(
makeSecurityTokenDescriptor(sSKey, claimList));
return tokenHandler.WriteToken(jwtToken);
}
public static ClaimsPrincipal ValidateJwtToken(string jwtToken)
{
var tokenHandler = new JwtSecurityTokenHandler() { RequireExpirationTime = true };
// Parse JWT from the Base64UrlEncoded wire form
//(<Base64UrlEncoded header>.<Base64UrlEncoded body>.<signature>)
JwtSecurityToken parsedJwt = tokenHandler.ReadToken(jwtToken) as JwtSecurityToken;
TokenValidationParameters validationParams =
new TokenValidationParameters()
{
AllowedAudience = SecurityConstants.TokenAudience,
ValidIssuer = SecurityConstants.TokenIssuer,
ValidateIssuer = true,
SigningToken = new BinarySecretSecurityToken(SecurityConstants.KeyForHmacSha256),
};
return tokenHandler.ValidateToken(parsedJwt, validationParams);
}
private static SecurityTokenDescriptor makeSecurityTokenDescriptor(
InMemorySymmetricSecurityKey sSKey, List<Claim> claimList)
{
var now = DateTime.UtcNow;
Claim[] claims = claimList.ToArray();
return new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
TokenIssuerName = SecurityConstants.TokenIssuer,
AppliesToAddress = SecurityConstants.TokenAudience,
Lifetime = new Lifetime(now, now.AddMinutes(SecurityConstants.TokenLifetimeMinutes)),
SigningCredentials = new SigningCredentials(sSKey,
"http://www.w3.org/2001/04/xmldsig-more#hmac-sha256",
"http://www.w3.org/2001/04/xmlenc#sha256"),
};
}
}
I have a message handler to intercept the requests and I verify the validity of token except for the route for log in, using TokenManager.ValidateJwtToken()
above.
To create the token, in the LoginController
, I have the following code:
[Route("login")]
[HttpPost]
public HttpResponseMessage Login(LoginBindingModel login)
{
if (login.Username == "admin" && login.Password == "password") //Do real auth
{
string role = "Librarian";
var jwtToken = TokenManager.CreateJwtToken(login.Username, role);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
UserName = login.Username,
Roles = role,
AccessToken = jwtToken
}, Configuration.Formatters.JsonFormatter)
};
}
return new HttpResponseMessage(HttpStatusCode.BadRequest);
}
The full working code is available here and the instructions to run the sample are in the Wiki.