Introduction

I was trying to implement JWT Auth in the Web API in my Angular 2 client-side application. But while searching on the internet, I could not find a simple solution. Finally, I learned and implemented the process successfully. Here, I am sharing the steps involved in solving this problem. This will help you and will definitely save your time too.

Source code.

Prerequisites
  1. Web API in ASP.NET Core with JWT Authentication Project solution .
  2. Angular2/4 for a client-side application.

See the project stucture below.

JSON Web Token

Step 1 - Create ASP.NET Core Web API Project

  1. Open Visual Studio 2017 and go to File >> New >> Project
  2. Select the project template.

    JSON Web Token

  3. Right click the Solution Explorer and select Add -> New Project->Class Librabry.

    JSON Web Token

Fitness.JWT.API Project

I would like to explain the highlighted part of the project source code for enabling JWT Authentication.

JSON Web Token

Using the code

Blocks of code should look like this.

startup.cs

Configuring secret key, allowing cross-origin, and applying User policy authentication.

  1. //
  2. public IConfigurationRoot Configuration { get; }
  3. //SecretKey for Authentication
  4. private const string SecretKey = "ABCneedtogetthisfromenvironmentXYZ";
  5. private readonly SymmetricSecurityKey _signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
  6. // This method gets called by the runtime. Use this method to add services to the container.
  7. public void ConfigureServices(IServiceCollection services)
  8. {
  9. // Add framework services.
  10. // services.AddMvc();
  11. // Add framework services.
  12. // Add framework services.
  13. services.AddCors(options =>
  14. {
  15. options.AddPolicy("CorsPolicy",//Allow Cross origin
  16. builder => builder.AllowAnyOrigin()
  17. .AllowAnyMethod()
  18. .AllowAnyHeader()
  19. .AllowCredentials());
  20. });
  21. services.AddOptions();
  22. // Make authentication compulsory across the board (i.e. shut
  23. // down EVERYTHING unless explicitly opened up).
  24. services.AddMvc(config =>
  25. {
  26. var policy = new AuthorizationPolicyBuilder()
  27. .RequireAuthenticatedUser()
  28. .Build();
  29. config.Filters.Add(new AuthorizeFilter(policy));
  30. });
  31. // Use policy auth.
  32. services.AddAuthorization(options =>
  33. {
  34. options.AddPolicy("FitnessJWT",
  35. policy => policy.RequireClaim("FitnessJWT", "FitnessUser"));
  36. });
  37. // Get options from app settings
  38. var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
  39. // Configure JwtIssuerOptions
  40. services.Configure<JwtIssuerOptions>(options =>
  41. {
  42. options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
  43. options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
  44. options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
  45. });
  46. }
  47. //

JwtIssuerOptions.cs

This is the class file which is responsible to create Auth unique ticket on the server.

  1. //
  2. public class JwtIssuerOptions
  3. {
  4. /// <summary>
  5. /// "iss" (Issuer) Claim
  6. /// </summary>
  7. /// <remarks>The "iss" (issuer) claim identifies the principal that issued the
  8. /// JWT. The processing of this claim is generally application specific.
  9. /// The "iss" value is a case-sensitive string containing a StringOrURI
  10. /// value. Use of this claim is OPTIONAL.</remarks>
  11. public string Issuer { get; set; }
  12. /// <summary>
  13. /// "sub" (Subject) Claim
  14. /// </summary>
  15. /// <remarks> The "sub" (subject) claim identifies the principal that is the
  16. /// subject of the JWT. The claims in a JWT are normally statements
  17. /// about the subject. The subject value MUST either be scoped to be
  18. /// locally unique in the context of the issuer or be globally unique.
  19. /// The processing of this claim is generally application specific. The
  20. /// "sub" value is a case-sensitive string containing a StringOrURI
  21. /// value. Use of this claim is OPTIONAL.</remarks>
  22. public string Subject { get; set; }
  23. /// <summary>
  24. /// "aud" (Audience) Claim
  25. /// </summary>
  26. /// <remarks>The "aud" (audience) claim identifies the recipients that the JWT is
  27. /// intended for. Each principal intended to process the JWT MUST
  28. /// identify itself with a value in the audience claim. If the principal
  29. /// processing the claim does not identify itself with a value in the
  30. /// "aud" claim when this claim is present, then the JWT MUST be
  31. /// rejected. In the general case, the "aud" value is an array of case-
  32. /// sensitive strings, each containing a StringOrURI value. In the
  33. /// special case when the JWT has one audience, the "aud" value MAY be a
  34. /// single case-sensitive string containing a StringOrURI value. The
  35. /// interpretation of audience values is generally application specific.
  36. /// Use of this claim is OPTIONAL.</remarks>
  37. public string Audience { get; set; }
  38. /// <summary>
  39. /// "nbf" (Not Before) Claim (default is UTC NOW)
  40. /// </summary>
  41. /// <remarks>The "nbf" (not before) claim identifies the time before which the JWT
  42. /// MUST NOT be accepted for processing. The processing of the "nbf"
  43. /// claim requires that the current date/time MUST be after or equal to
  44. /// the not-before date/time listed in the "nbf" claim. Implementers MAY
  45. /// provide for some small leeway, usually no more than a few minutes, to
  46. /// account for clock skew. Its value MUST be a number containing a
  47. /// NumericDate value. Use of this claim is OPTIONAL.</remarks>
  48. public DateTime NotBefore => DateTime.UtcNow;
  49. /// <summary>
  50. /// "iat" (Issued At) Claim (default is UTC NOW)
  51. /// </summary>
  52. /// <remarks>The "iat" (issued at) claim identifies the time at which the JWT was
  53. /// issued. This claim can be used to determine the age of the JWT. Its
  54. /// value MUST be a number containing a NumericDate value. Use of this
  55. /// claim is OPTIONAL.</remarks>
  56. public DateTime IssuedAt => DateTime.UtcNow;
  57. /// <summary>
  58. /// Set the timespan the token will be valid for (default is 3 min/180 seconds)
  59. /// </summary>
  60. public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(1);
  61. /// <summary>
  62. /// "exp" (Expiration Time) Claim (returns IssuedAt + ValidFor)
  63. /// </summary>
  64. /// <remarks>The "exp" (expiration time) claim identifies the expiration time on
  65. /// or after which the JWT MUST NOT be accepted for processing. The
  66. /// processing of the "exp" claim requires that the current date/time
  67. /// MUST be before the expiration date/time listed in the "exp" claim.
  68. /// Implementers MAY provide for some small leeway, usually no more than
  69. /// a few minutes, to account for clock skew. Its value MUST be a number
  70. /// containing a NumericDate value. Use of this claim is OPTIONAL.</remarks>
  71. public DateTime Expiration => IssuedAt.Add(ValidFor);
  72. /// <summary>
  73. /// "jti" (JWT ID) Claim (default ID is a GUID)
  74. /// </summary>
  75. /// <remarks>The "jti" (JWT ID) claim provides a unique identifier for the JWT.
  76. /// The identifier value MUST be assigned in a manner that ensures that
  77. /// there is a negligible probability that the same value will be
  78. /// accidentally assigned to a different data object; if the application
  79. /// uses multiple issuers, collisions MUST be prevented among values
  80. /// produced by different issuers as well. The "jti" claim can be used
  81. /// to prevent the JWT from being replayed. The "jti" value is a case-
  82. /// sensitive string. Use of this claim is OPTIONAL.</remarks>
  83. public Func<Task<string>> JtiGenerator =>
  84. () => Task.FromResult(Guid.NewGuid().ToString());
  85. /// <summary>
  86. /// The signing key to use when generating tokens.
  87. /// </summary>
  88. public SigningCredentials SigningCredentials { get; set; }
  89. }
  90. //

JwtController.cs

it is a controller where the anonymous users will login and which creates the JWT security token, encodes it, and sends back to the client as a response with policy.

identity.FindFirst("FitnessJWT")

Look into the below code.

  1. [HttpPost]
  2. [AllowAnonymous]
  3. public async Task<IActionResult> Get([FromBody] ApplicationUser applicationUser)
  4. {
  5. var identity = await GetClaimsIdentity(applicationUser);
  6. if (identity == null)
  7. {
  8. _logger.LogInformation($"Invalid username ({applicationUser.UserName}) or password ({applicationUser.Password})");
  9. return BadRequest("Invalid credentials");
  10. }
  11. var claims = new[]
  12. {
  13. new Claim(JwtRegisteredClaimNames.Sub, applicationUser.UserName),
  14. new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
  15. new Claim(JwtRegisteredClaimNames.Iat, ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
  16. identity.FindFirst("FitnessJWT")
  17. };
  18. // Create the JWT security token and encode it.
  19. var jwt = new JwtSecurityToken(
  20. issuer: _jwtOptions.Issuer,
  21. audience: _jwtOptions.Audience,
  22. claims: claims,
  23. notBefore: _jwtOptions.NotBefore,
  24. expires: _jwtOptions.Expiration,
  25. signingCredentials: _jwtOptions.SigningCredentials);
  26. var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
  27. // Serialize and return the response
  28. var response = new
  29. {
  30. access_token = encodedJwt,
  31. expires_in = (int)_jwtOptions.ValidFor.TotalSeconds,
  32. State=1,
  33. expire_datetime= _jwtOptions.IssuedAt
  34. };
  35. var json = JsonConvert.SerializeObject(response, _serializerSettings);
  36. return new OkObjectResult(json);
  37. }

JwtAuthTestController.cs

This is the controller where I have defined the policy [Authorize(Policy = "FitnessJWT")]. So, when a user requsets to the Controller, then it matches the policy and secret key. Then only the response is returned to the client.

  1. [HttpGet("[action]")]
  2. [Authorize(Policy = "FitnessJWT")]
  3. public IActionResult WeatherForecasts()
  4. {
  5. var rng = new Random();
  6. List<WeatherForecast> lstWeatherForeCast = new List<WeatherForecast>();
  7. for (int i = 0; i < 5; i++)
  8. {
  9. WeatherForecast obj = new WeatherForecast();
  10. obj.DateFormatted = DateTime.Now.AddDays(i).ToString("d");
  11. obj.TemperatureC = rng.Next(-20, 55);
  12. obj.Summary = Summaries[rng.Next(Summaries.Length)];
  13. lstWeatherForeCast.Add(obj);
  14. }
  15. var response = new
  16. {
  17. access_token = lstWeatherForeCast,
  18. State = 1
  19. };
  20. var json = JsonConvert.SerializeObject(response, _serializerSettings);
  21. return new OkObjectResult(json);
  22. }

Step 2 - Angular2/4 for Client side application.

I would like to add one note that I have not focused on the UI part too much but I have tried to implement JWT Auth from Angular 2/4 Application.

Fitness.App.UI Solution

login.component.ts

This is the login module with TypeScript where we authenticate a user by passing Username and password.

  1. import { Component } from '@angular/core';
  2. import { Router } from '@angular/router';
  3. import { AuthService } from "../../../app/services/auth.service";
  4. import { LoginModel } from "../../model/login.model";
  5. @Component({
  6. selector: 'Fitness-Login',
  7. templateUrl: './login.component.html',
  8. styleUrls: ['./login.component.css'],
  9. providers: [AuthService]
  10. })
  11. export class LoginComponent {
  12. loginModel = new LoginModel();
  13. constructor(private router: Router, private authService: AuthService) {
  14. }
  15. login() {
  16. this.authService.login(this.loginModel.userName, this.loginModel.password)
  17. .then(result => {
  18. if (result.State == 1) {
  19. this.router.navigate(["/nav-menu"]);
  20. }
  21. else {
  22. alert(result.access_token);
  23. }
  24. });
  25. }
  26. }

auth.service.ts

Authentication service validates the credentials and redirects to the homepage.

  1. login(userName: string, password: string): Promise<ResponseResult> {
  2. let data = {
  3. "userName": userName,
  4. "password": password
  5. }
  6. let headers = new Headers({ 'Content-Type': 'application/json' });
  7. let applicationUser = JSON.stringify(data);
  8. let options = new RequestOptions({ headers: headers });
  9. if (this.checkLogin()) {
  10. return this.authPost(this.localUrl + '/api/Jwt', applicationUser, options);
  11. }
  12. else {
  13. return this.http.post(this.localUrl + '/api/Jwt', applicationUser, options).toPromise()
  14. .then(
  15. response => {
  16. let result = response.json() as ResponseResult;
  17. if (result.State == 1) {
  18. let json = result.access_token as any;
  19. localStorage.setItem(this.tokeyKey, json);
  20. localStorage.setItem(this.tokeyExpKey, result.expire_datetime);
  21. this.sg['isUserExist'] = true;
  22. }
  23. return result;
  24. }
  25. )
  26. .catch(this.handleError);
  27. }
  28. }

app.module.client.ts

{ provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' }, path on the JWT WEB API.

You need to change the localhost API based on your machine URL.

  1. @NgModule({
  2. bootstrap: sharedConfig.bootstrap,
  3. declarations: sharedConfig.declarations,
  4. imports: [
  5. BrowserModule,
  6. FormsModule,
  7. HttpModule,
  8. ...sharedConfig.imports
  9. ],
  10. providers: [
  11. //{ provide: 'ORIGIN_URL', useValue: location.origin },
  12. { provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' },
  13. AuthService, AuthGuard, SimpleGlobal
  14. ]
  15. })
  16. export class AppModule {
  17. }

To run the application, you need to set he project as below.

Run the solution with multiple startup projects. Then, in the browser, both - the client app and the Web API Service will start in two tabs.

JSON Web Token

The output of the application is given below.

JSON Web Token
JSON Web Token
User Name: Test

Password - Test

Then, it will redirect you to the nav menu page as below.

JSON Web Token