Introduction
I was trying to implement JWT Auth in web API. I want to consume the Web API in my Angular 2 client side application. But while searching on the internet, I didn't find any correct solution for my problem with Project architecture setup. Finally, I am able to get the actual steps how to approach this problem, which may help you to save your time to check a lot of stuff from this solution. So here is the detailed explanation below.
Link for source code: https://github.com/rajeshdas85/JwtAuthentication
- Web API in ASP.NET Core with JWT authentication Project solution
- Angular2/4 for Client side application
See the project structure below:
Background
Step 1
Create ASP.NET Core Web API Project
- Open “Visual Studio 2017” -> go to “File” menu -> New -> Project
- Select project template.
- Right click on Solution Explorer -> Add -> New Project->Class Library.
Fitness.JWT.API Project Explanation
I would like to explain about the highlighted part of the project source code for enabling Jwt Authentication.
Using the Code
startup.cs
Configuring secretkey
, allowing cross origin and applying Use policy auth.
public IConfigurationRoot Configuration { get; }
private const string SecretKey = "ABCneedtogetthisfromenvironmentXYZ";
private readonly SymmetricSecurityKey _signingKey =
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey));
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
services.AddOptions();
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
services.AddAuthorization(options =>
{
options.AddPolicy("FitnessJWT",
policy => policy.RequireClaim("FitnessJWT", "FitnessUser"));
});
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials
(_signingKey, SecurityAlgorithms.HmacSha256);
});
}
JwtIssuerOptions.cs
This is the class file which is responsible for creating Auth unique ticket in server.
public class JwtIssuerOptions
{
public string Issuer { get; set; }
public string Subject { get; set; }
public string Audience { get; set; }
public DateTime NotBefore => DateTime.UtcNow;
public DateTime IssuedAt => DateTime.UtcNow;
public TimeSpan ValidFor { get; set; } = TimeSpan.FromMinutes(1);
public DateTime Expiration => IssuedAt.Add(ValidFor);
public Func<Task<string>> JtiGenerator =>
() => Task.FromResult(Guid.NewGuid().ToString());
public SigningCredentials SigningCredentials { get; set; }
}
JwtController.cs
It is the controller where anonymous user will login and which creates the JWT security token and encodes it and sends back to client as Response with policy.
identity.FindFirst("FitnessJWT")
Please see the below code:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Get([FromBody] ApplicationUser applicationUser)
{
var identity = await GetClaimsIdentity(applicationUser);
if (identity == null)
{
_logger.LogInformation($"Invalid username ({applicationUser.UserName})
or password ({applicationUser.Password})");
return BadRequest("Invalid credentials");
}
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, applicationUser.UserName),
new Claim(JwtRegisteredClaimNames.Jti, await _jwtOptions.JtiGenerator()),
new Claim(JwtRegisteredClaimNames.Iat,
ToUnixEpochDate(_jwtOptions.IssuedAt).ToString(), ClaimValueTypes.Integer64),
identity.FindFirst("FitnessJWT")
};
var jwt = new JwtSecurityToken(
issuer: _jwtOptions.Issuer,
audience: _jwtOptions.Audience,
claims: claims,
notBefore: _jwtOptions.NotBefore,
expires: _jwtOptions.Expiration,
signingCredentials: _jwtOptions.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
access_token = encodedJwt,
expires_in = (int)_jwtOptions.ValidFor.TotalSeconds,
State=1,
expire_datetime= _jwtOptions.IssuedAt
};
var json = JsonConvert.SerializeObject(response, _serializerSettings);
return new OkObjectResult(json);
}
JwtAuthTestController.cs
This controller where I have defined the policy [Authorize(Policy = "FitnessJWT")]
so when user requests the controller, then it must match with the policy and secret key, then the response will return to client.
[HttpGet("[action]")]
[Authorize(Policy = "FitnessJWT")]
public IActionResult WeatherForecasts()
{
var rng = new Random();
List<WeatherForecast> lstWeatherForeCast = new List<WeatherForecast>();
for (int i = 0; i < 5; i++)
{
WeatherForecast obj = new WeatherForecast();
obj.DateFormatted = DateTime.Now.AddDays(i).ToString("d");
obj.TemperatureC = rng.Next(-20, 55);
obj.Summary = Summaries[rng.Next(Summaries.Length)];
lstWeatherForeCast.Add(obj);
}
var response = new
{
access_token = lstWeatherForeCast,
State = 1
};
var json = JsonConvert.SerializeObject(response, _serializerSettings);
return new OkObjectResult(json);
}
Step 2: Angular2/4 for Client Side Application
I would like to add one of this. I do not have much focus on the UI part, but I have tried to implement JWT Auth from Angualar2/4 Application.
Fitness.App.UI Solution
login.component.ts
Login module with typescript by passing Username and password:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from "../../../app/services/auth.service";
import { LoginModel } from "../../model/login.model";
@Component({
selector: 'Fitness-Login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css'],
providers: [AuthService]
})
export class LoginComponent {
loginModel = new LoginModel();
constructor(private router: Router, private authService: AuthService) {
}
login() {
this.authService.login(this.loginModel.userName, this.loginModel.password)
.then(result => {
if (result.State == 1) {
this.router.navigate(["/nav-menu"]);
}
else {
alert(result.access_token);
}
});
}
}
auth.service.ts
Authentication service which validates credentials and redirects to home page.
login(userName: string, password: string): Promise<ResponseResult> {
let data = {
"userName": userName,
"password": password
}
let headers = new Headers({ 'Content-Type': 'application/json' });
let applicationUser = JSON.stringify(data);
let options = new RequestOptions({ headers: headers });
if (this.checkLogin()) {
return this.authPost(this.localUrl + '/api/Jwt', applicationUser, options);
}
else {
return this.http.post(this.localUrl + '/api/Jwt', applicationUser, options).toPromise()
.then(
response => {
let result = response.json() as ResponseResult;
if (result.State == 1) {
let json = result.access_token as any;
localStorage.setItem(this.tokeyKey, json);
localStorage.setItem(this.tokeyExpKey, result.expire_datetime);
this.sg['isUserExist'] = true;
}
return result;
}
)
.catch(this.handleError);
}
}
app.module.client.ts
{ provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' }
, path on the JWT WEB API.
You need to change the local host API based on your machine URL.
@NgModule({
bootstrap: sharedConfig.bootstrap,
declarations: sharedConfig.declarations,
imports: [
BrowserModule,
FormsModule,
HttpModule,
...sharedConfig.imports
],
providers: [
{ provide: 'ORIGIN_URL', useValue: 'http://localhost:57323' },
AuthService, AuthGuard, SimpleGlobal
]
})
export class AppModule {
}
To Run the Application, You Need to Set the Project as Below:
Run the solution with multiple startup projects.
Then in browser in two tabs, both client app and web API service will start.
Output on the Application Is Below
User Name:Test and Password:Test
Then, it will redirect to nav menu page as below:
Points of Interest
I enjoyed a lot while writing this blog.
History
Please update the code if any improvement is needed.