I have a running members only website that works as it should with login etc. I now need to support Single Sign On where I will recieve the username of the profile in a JWT token, and then log the user in. The external system that will send this request, does NOT know any other information than the Username - so the password is not shared in the token, nor is it possible.
JWT has been chosen as the method of choice to enable this. We are going to use a secret key to exchange the information, the secret key is ofcourse only know by the sender and reciever.
What I am looking for is a simple example of how this could look code-wise, both in the sender end and in the recieving end. The reciever end will be a WebAPI endpoint that will handle the login and then redirect to the frontpage of the members-only website if the login is successfull.
It is developed in C#.
CodePudding user response:
Elementary tips
Usually and commonly, the user/password are exchanged for a new JWT
JWT should not store anything sensible like a password. But some companies like Microsoft store not a sensible values but a lot of information
The security platform returns a response with data in a syntax that client (c# in your case) understand if token is legit, valid, not expired, user exist, user is allowed to perform the operation, etc. I used this in some implementation in which I said: "User is allowed to perform this operation and this is its username"
endpoint:
acme-security.com/v1/oauth2/token/validate
request:
eyJ0eXAiOiJKV1Qi...
response:
{ "isAllowed": true, "subject": "[email protected]" }
Also you can do this with an api gateway
On a more real scenario, we need more than a simple jwt validation. We need to ensure that user admin/guest are allowed to execute specific endpoints. We don't want that a employee invoke directly the payment endpoint set its own salary :/
Follow the oauth2 spec if you will implement your own security platform
Jwt should be sent as Authorization Header
IMPORTANT: If you will implement your own jwt generation and validation, use ENV variables to hide it the secret and protect it as much as you can for your production environment
JWT handling with c# netcore 5
If the previous steps are understood, you only need one of these approaches:
- Use some IAM or security platforms like auth0, okta, keycloack, etc
- A simple c# web/microservice/api (that also functions as security platform) to receive token and return if user is allowed
- A class to to the validation inside of the target microservice
JWT Generation
public string GenerateToken(User user)
{
// generate token that is valid for 7 days
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(SUPER_SECRET_KEY);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] { new Claim("id", user.Id.ToString()) }),
Expires = DateTime.UtcNow.AddDays(7),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
JWT Decode or validation
public int? ValidateToken(string token)
{
if (token == null)
return null;
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(SUPER_SECRET_KEY);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}
This code throws errors when token is expired or some buddy tries to send a jwt without your SUPER_SECRET_KEY
You could use these snippets directly in your token generation and inside of your endpoints
[HttpGet]
public ActionResult<List<Dictionary<string, object>>> findAllEmployees()
{
//get jwt from http received headers
validationResponse = ValidateToken(jwt)
//if you have a security platform, callhere instead
//ValidateToken(jwt)
//if validationResponse.isAllowed ....
var list = this.employeeService.findAllEmployees();
return Ok(list);
}
or with a middleware like a pro, to keep clean your final controllers