This is the third part of Building Simple Membership system using ASP.NET Identity 2.1, ASP.NET Web API 2.2 and AngularJS. The topics we’ll cover are:
The source code for this tutorial is available on GitHub.
Implement JSON Web Tokens Authentication in ASP.NET Web API and Identity 2.1
Currently our API doesn’t support authentication and authorization, all the requests we receive to any end point are done anonymously, In this post, we’ll configure our API which will act as our Authorization Server and Resource Server at the same time to issue JSON Web Tokens for authenticated users and those users will present this JWT to the protected end points in order to access it and process the request.
I will use a step by step approach as usual to implement this, but I highly recommend you to read the post JSON Web Token in ASP.NET Web API 2 before completing this one; where I cover deeply what is JSON Web Tokens, the benefits of using JWT over default access tokens, and how they can be used to decouple Authorization server from Resource server. In this tutorial and for the sake of keeping it simple; both OAuth 2.0 roles (Authorization Server and Recourse Server) will live in the same API.
Step 1: Implement OAuth 2.0 Resource Owner Password Credential Flow
We are going to build an API which will be consumed by a trusted client (AngularJS front-end) so we are only interested in implementing a single OAuth 2.0 flow where the registered user will present username and password to a specific end point, and the API will validate those credentials, and if all is valid, it will return a JWT for the user where the client application used by the user should store it securely and locally in order to present this JWT with each request to any protected end point.
The nice thing about this JWT is that it is a self contained token which contains all user claims and roles inside it, so there is no need to do any extra DB queries to fetch those values for the authenticated user. This JWT token will be configured to expire after 1 day of its issue date, so the user is requested to provide credentials again in order to obtain new JWT token.
If you are interested in knowing how to implement sliding expiration tokens and how you can keep the user logged in; I recommend you read my other post Enable OAuth Refresh Tokens in AngularJS App which covers this in depth, but adds more complexity to the solution. To keep this tutorial simple, we’ll not add refresh tokens here but you can refer to the post and implement it.
To implement the Resource Owner Password Credential flow; we need to add a new folder named “Providers” then add a new class named “CustomOAuthProvider
”, after you add then paste the code below:
public class CustomOAuthProvider : OAuthAuthorizationServerProvider
{
public override Task ValidateClientAuthentication
(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials
(OAuthGrantResourceOwnerCredentialsContext context)
{
var allowedOrigin = "*";
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin",
new[] { allowedOrigin });
var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
if (!user.EmailConfirmed)
{
context.SetError("invalid_grant", "User did not confirm email.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager, "JWT");
var ticket = new AuthenticationTicket(oAuthIdentity, null);
context.Validated(ticket);
}
}
This class inherits from class “OAuthAuthorizationServerProvider
” and overrides the below two methods:
- As you notice, the “
ValidateClientAuthentication
” is empty, we are considering the request valid always, because in our implementation our client (AngularJS front-end) is a trusted client and we do not need to validate it. - The method “
GrantResourceOwnerCredentials
” is responsible for receiving the username and password from the request and validating them against our ASP.NET 2.1 Identity system, if the credentials are valid and the email is confirmed, we are building an identity for the logged in user, this identity will contain all the roles and claims for the authenticated user, until now we didn’t cover roles and claims part of the tutorial, but for the mean time you can consider all users registered in our system without any roles or claims mapped to them. - The method “
GenerateUserIdentityAsync
” is not implemented yet, we’ll add this helper method in the next step. This method will be responsible to fetch the authenticated user identity from the database and returns an object of type “ClaimsIdentity
”. - Lastly, we are creating an Authentication ticket which contains the identity for the authenticated user, and when we call “
context.Validated(ticket)
” this will transfer this identity to an OAuth 2.0 bearer access token.
Step 2: Add Method “GenerateUserIdentityAsync” to “ApplicationUser” Class
Now we’ll add the helper method which will be responsible to get the authenticated user identity (all roles and claims mapped to the user). The “UserManager
” class contains a method named “CreateIdentityAsync
” to do this task, it will basically query the DB and get all the roles and claims for this user, to implement this open class “ApplicationUser
” and paste the code below:
public async Task<ClaimsIdentity> GenerateUserIdentityAsync
(UserManager<ApplicationUser> manager, string authenticationType)
{
var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
return userIdentity;
}
Step 3: Issue JSON Web Tokens instead of Default Access Tokens
Now we want to configure our API to issue JWT tokens instead of default access tokens, to understand what is JWT and why it is better to use it, you can refer back to this post.
First thing is we need to install 2 NueGet packages as below:
Install-package System.IdentityModel.Tokens.Jwt -Version 4.0.1
Install-package Thinktecture.IdentityModel.Core -Version 1.3.0
There is no direct support for issuing JWT in ASP.NET Web API, so in order to start issuing JWTs, we need to implement this manually by implementing the interface “ISecureDataFormat
” and implement the method “Protect
”.
To implement this, add new file named “CustomJwtFormat
” under folder “Providers” and paste the code below:
public class CustomJwtFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string _issuer = string.Empty;
public CustomJwtFormat(string issuer)
{
_issuer = issuer;
}
public string Protect(AuthenticationTicket data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
string symmetricKeyAsBase64 = ConfigurationManager.AppSettings["as:AudienceSecret"];
var keyByteArray = TextEncodings.Base64Url.Decode(symmetricKeyAsBase64);
var signingKey = new HmacSigningCredentials(keyByteArray);
var issued = data.Properties.IssuedUtc;
var expires = data.Properties.ExpiresUtc;
var token = new JwtSecurityToken(_issuer, audienceId, data.Identity.Claims,
issued.Value.UtcDateTime, expires.Value.UtcDateTime, signingKey);
var handler = new JwtSecurityTokenHandler();
var jwt = handler.WriteToken(token);
return jwt;
}
public AuthenticationTicket Unprotect(string protectedText)
{
throw new NotImplementedException();
}
}
What we’ve implemented in this class is the following:
- The class “
CustomJwtFormat
” implements the interface “ISecureDataFormat<AuthenticationTicket>
”, the JWT generation will take place inside method “Protect
”. - The constructor of this class accepts the “
Issuer
” of this JWT which will be our API. This API acts as Authorization and Resource Server on the same time, this can be string or URI, in our case we’ll fix it to URI. - Inside “
Protect
” method, we are doing the following:
- As we stated before, this API serves as Resource and Authorization Server at the same time, so we are fixing the Audience Id and Audience Secret (Resource Server) in web.config file, this Audience Id and Secret will be used for HMAC265 and hash the JWT token, I’ve used this implementation to generate the Audience Id and Secret.
- Do not forget to add 2 new keys “
as:AudienceId
” and “as:AudienceSecret
” to the web.config AppSettings
section. - Then we prepare the raw data for the JSON Web Token which will be issued to the requester by providing the issuer, audience, user claims, issue date, expiry date, and the signing key which will sign (hash) the JWT payload.
- Lastly, we serialize the JSON Web Token to a
string
and return it to the requester.
- By doing this, the requester for an OAuth 2.0 access token from our API will receive a signed token which contains claims for an authenticated Resource Owner (User) and this access token is intended to certain (Audience) as well.
Step 4: Add Support for OAuth 2.0 JWT Generation
Till this moment, we didn’t configure our API to use OAuth 2.0 Authentication workflow, to do so open class “Startup
” and add new method named “ConfigureOAuthTokenGeneration
” as shown below:
private void ConfigureOAuthTokenGeneration(IAppBuilder app)
{
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:59822")
};
app.UseOAuthAuthorizationServer(OAuthServerOptions);
}
What we’ve implemented here is the following:
- The path for generating JWT will be as :”http://localhost:59822/oauth/token”.
- We’ve specified the expiry for token to be 1 day.
- We’ve specified the implementation on how to validate the Resource owner user credential in a custom class named “
CustomOAuthProvider
”. - We’ve specified the implementation on how to generate the access token using JWT formats, this custom class named “
CustomJwtFormat
” will be responsible for generating JWT instead of default access token using DPAPI, note that both format will use Bearer scheme.
Do not forget to call the new method “ConfigureOAuthTokenGeneration
” in the Startup “Configuration
” as the class below:
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
}
Our API currently is ready to start issuing JWT access token, so test this out, we can issue HTTP POST request as the image below, and we should receive a valid JWT token for the next 24 hours and accepted only by our API.
Step 5: Protect the Existing End Points with [Authorize] Attribute
Now we’ll visit all the end points we have created earlier in previous posts in the “AccountsController
” class, and attribute the end points which need to be protected (only authentication user with valid JWT access token can access it) with the [Authorize]
attribute as shown below:
GetUsers
, GetUser
, GetUserByName
, and DeleteUser
end points should be accessed by users enrolled in Role “Admin
”. Roles Authorization is not implemented yet and for now we will only allow any authentication user to access it, the code change will be as simple as shown below:
[Authorize]
[Route("users")]
public IHttpActionResult GetUsers()
{}
[Authorize]
[Route("user/{id:guid}", Name = "GetUserById")]
public async Task<IHttpActionResult> GetUser(string Id)
{}
[Authorize]
[Route("user/{username}")]
public async Task<IHttpActionResult> GetUserByName(string username)
{
}
[Authorize]
[Route("user/{id:guid}")]
public async Task<IHttpActionResult> DeleteUser(string id)
{
}
CreateUser
and ConfirmEmail
endpoints should be accessed anonymously always, so we need to attribute it with [AllowAnonymous]
as shown below:
[AllowAnonymous]
[Route("create")]
public async Task<IHttpActionResult> CreateUser(CreateUserBindingModel createUserModel)
{
}
[AllowAnonymous]
[HttpGet]
[Route("ConfirmEmail", Name = "ConfirmEmailRoute")]
public async Task<IHttpActionResult>
ConfirmEmail(string userId = "", string code = "")
{
}
ChangePassword
endpoint should be accessed by the authenticated user only, so we’ll attribute it with [Authorize]
attribute as given below:
[Authorize]
[Route("ChangePassword")]
public async Task<IHttpActionResult> ChangePassword(ChangePasswordBindingModel model)
{
}
Step 6: Consume JSON Web Tokens
Now if we tried to obtain an access token by sending a request to the end point “oauth/token
”, then try to access one of the protected end points we’ll receive 401 Unauthorized status, the reason for this that our API doesn’t understand those JWT tokens issued by our API yet, to fix this we need to the following:
Install the below NuGet package:
Install-Package Microsoft.Owin.Security.Jwt -Version 3.0.0
The package “Microsoft.Owin.Security.Jwt
” is responsible for protecting the Resource server resources using JWT, it only validate and de-serialize JWT tokens.
Now back to our “Startup
” class, we need to add the below method “ConfigureOAuthTokenConsumption
” as the below:
private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
var issuer = "http://localhost:59822";
string audienceId = ConfigurationManager.AppSettings["as:AudienceId"];
byte[] audienceSecret = TextEncodings.Base64Url.Decode
(ConfigurationManager.AppSettings["as:AudienceSecret"]);
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { audienceId },
IssuerSecurityTokenProviders = new IIssuerSecurityTokenProvider[]
{
new SymmetricKeyIssuerSecurityTokenProvider(issuer, audienceSecret)
}
});
}
This step will configure our API to trust tokens issued by our Authorization server only, in our case the Authorization and Resource Server are the same server (http://localhost:59822), notice how we are providing the values for audience, and the audience secret we used to generate and issue the JSON Web Token in step 3.
By providing those values to the “JwtBearerAuthentication
” middleware, our API will be able to consume only JWT tokens issued by our trusted Authorization server, any other JWT tokens from any other Authorization server will be rejected.
Lastly, we need to call the method “ConfigureOAuthTokenConsumption
” in the “Configuration
” method as the below:
public void Configuration(IAppBuilder app)
{
HttpConfiguration httpConfig = new HttpConfiguration();
ConfigureOAuthTokenGeneration(app);
ConfigureOAuthTokenConsumption(app);
}
Step 7: Final Testing
All the pieces should be in place now, to test this, we will obtain JWT access token for the user “SuperPowerUser
” by issuing POST
request to the end point “oauth/token
”.
Then, we will use the JWT received to access protected end point such as “ChangePassword
”, if you remember once we added this end point, we were not able to test it directly because it was anonymous and inside its implementation, we were calling the method “User.Identity.GetUserId()
”. This method will return nothing for anonymous user, but after we’ve added the [Authorize]
attribute, any user needs to access this end point should be authenticated and has a valid JWT.
To test this out, we will issue POST
request to the end point “/accounts/ChangePassword
” as the image below, notice that we are setting the Authorization header using Bearer scheme setting its value to the JWT we received for the user “SuperPowerUser
”. If all is valid, we will receive 200 OK status and the user password should be updated.
The source code for this tutorial is available on GitHub.
In the next post, we’ll see how we’ll implement Roles Based Authorization in our Identity service.
Follow me on Twitter @tjoudeh.
References
The post Implement OAuth JSON Web Tokens Authentication in ASP.NET Web API and Identity 2.1 – Part 3 appeared first on Bit of Technology.