Reputation: 13387
I am using OAuth to generate tokens for my application, specifically JWT. I have this code in my startup class:
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(DatabaseContext.Create);
app.CreatePerOwinContext<UserService>(UserService.Create);
app.CreatePerOwinContext<RoleService>(RoleService.Create);
// Plugin the OAuth bearer JSON Web Token tokens generation and Consumption will be here
var OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new OAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:58127")
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
As you can see I have set a custom OAuthProvider and for the AccessTokenFormat I am using a CustomJwtFormat. The OAuthProvider looks like this:
public class OAuthProvider : OAuthAuthorizationServerProvider
{
/// <summary>
/// Validate client authentication
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Validate all requests (because our front end is trusted)
context.Validated();
// Return nothing
return Task.FromResult<object>(null);
}
/// <summary>
/// Validate user credentials
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Allow any origin
var allowedOrigin = "*";
// Add the access control allow all to our headers
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// Get our user service
var service = context.OwinContext.GetUserManager<UserService>();
// Find out user
var user = await service.FindAsync(context.UserName, context.Password);
// If the user is not found
if (user == null)
{
// Set an error
context.SetError("invalid_grant", "The user name or password is incorrect.");
// Return from the function
return;
}
// If the user has not confirmed their account
if (!user.EmailConfirmed)
{
// Set an error
context.SetError("invalid_grant", "User did not confirm email.");
// Return from the function
return;
}
// Generate the identity for the user
var oAuthIdentity = await user.GenerateUserIdentityAsync(service, "JWT");
// Create a new ticket
var ticket = new AuthenticationTicket(oAuthIdentity, null);
// Add the ticked to the validated context
context.Validated(ticket);
}
}
which is pretty straight forward. Also, the CustomJwtFormat class looks like this:
/// <summary>
/// JWT Format
/// </summary>
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
// Create our private property
private readonly string issuer;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="issuer">The issuer</param>
public CustomJwtFormat(string issuer)
{
this.issuer = issuer;
}
/// <summary>
/// Method to create our JWT token
/// </summary>
/// <param name="data">The Authentication ticket</param>
/// <returns></returns>
public string Protect(AuthenticationTicket data)
{
// If no data is supplied, throw an exception
if (data == null)
throw new ArgumentNullException("data");
// Get our values from our appSettings
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
// Decode our secret and encrypt the bytes
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
// Get our issue and expire dates in UNIX timestamps
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
// Create our new token
var token = new JwtSecurityToken(this.issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
// Create a handler
var handler = new JwtSecurityTokenHandler();
// Write our token string
var jwt = handler.WriteToken(token);
// Return our token string
return jwt;
}
/// <summary>
///
/// </summary>
/// <param name="protectedText"></param>
/// <returns></returns>
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
This is the crucial bit. It uses Thinktecture to generate the token. The line that looks like this:
// Create our new token
var token = new JwtSecurityToken(this.issuer, audienceId, data.Identity.Claims, issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
// Create a handler
var handler = new JwtSecurityTokenHandler();
// Write our token string
var jwt = handler.WriteToken(token);
This returns what you would expect from the token (access_token, expires_in and token_type) but I would like to return some user information too. Things like username, roles, etc.
Anyone know how I can do this?
Upvotes: 2
Views: 1198
Reputation: 271
The username and roles are present in the authenticated identities claims and therefore persisted in the JWT that is sent back in the access token.
So the line:
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims,issued,expires,signingKey);
Inserts the claims from the auth'd identity:
i.e if I did this in the OAuth provider:
```
IList<Claim> claims = new List<Claim>();
if (context.UserName.Equals("spencer") && context.UserName.Equals(context.Password))
{
claims.Add(new Claim(ClaimTypes.Name, user.DisplayName));
claims.Add(new Claim(ClaimTypes.Role, "User"));
}
));
var claimIdentity = new ClaimsIdentity(claims);
var ticket = new AuthenticationTicket(claimIdentity, null);
//Now authed and claims are in my identity context
context.Validated(ticket);
```
So now when the JWT is generated those claims are in the token.
You can then decorate your Api Controllers with explicit roles which would then query the "Roles" type in the claimset. If the user doesn't have the role in the role claimset than a 401 is issued:
```
[Route]
[Authorize(Roles ="User,Admin")]
public IHttpActionResult Get()
{
return Ok<IEnumerable<Product>>(_products);
}
[Route]
[Authorize(Roles = "Admin")]
public IHttpActionResult Post(Product product)
{
_products.Add(product);
return Created(string.Empty, product);
}
```
So in the above example, if I generate a JWT as me "Spencer" i'm in the users role and the GET would be OK (200) whilst the POST would be Unauthorized (401).
Make sense?
Upvotes: 0