Skip to content

Commit c9dc9c8

Browse files
committed
code clean up; update Readme
1 parent 2c3b31c commit c9dc9c8

File tree

7 files changed

+54
-16
lines changed

7 files changed

+54
-16
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
This repository demos an ASP.NET Core web API application using JWT auth, and an integration testing project for a set of actions including login, logout, refresh token, impersonation, authentication, and authorization.
44

5+
## Medium Articles
6+
7+
- [Using JWT in ASP.NET Core](https://codeburst.io/using-jwt-in-asp-net-core-148fb72bed03)
8+
9+
In this article, I will show you how to implement an ASP.NET Core web API application using JWT authentication and authorization.
10+
511
## Solution Structure
612

713
This repository includes two applications: an Angular SPA in the `angular` folder, and an ASP.NET Core web API app in the `webapi` folder. The SPA makes HTTP requests to the server side (the `webapi` app) using an API BaseURL `https://localhost:5001`. The API BaseURL is set in the `environment.ts` file and the `environment.prod.ts` file, which can be modified based on your situation.

docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ services:
1212
api:
1313
build: ./webapi
1414
ports:
15-
- '5001'
15+
- '5001:5001'
1616
environment:
1717
- ASPNETCORE_ENVIRONMENT=Development
1818
- ASPNETCORE_URLS=https://+:5001;http://+:5000

webapi/JwtAuthDemo.IntegrationTests/JwtAuthManagerTests.cs

+30-2
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,37 @@ public void ShouldThrowExceptionWhenRefreshTokenUsingAnExpiredToken()
6464
new Claim(ClaimTypes.Name,userName),
6565
new Claim(ClaimTypes.Role, UserRoles.Admin)
6666
};
67-
var tokens = jwtAuthManager.GenerateTokens(userName, claims, now.AddMinutes(-jwtTokenConfig.RefreshTokenExpiration - 1));
6867

69-
var e = Assert.ThrowsException<SecurityTokenException>(() => jwtAuthManager.Refresh(tokens.RefreshToken.TokenString, tokens.AccessToken, now));
68+
var jwtAuthResult1 = jwtAuthManager.GenerateTokens(userName, claims, now.AddMinutes(-jwtTokenConfig.AccessTokenExpiration - 1).AddSeconds(1));
69+
jwtAuthManager.Refresh(jwtAuthResult1.RefreshToken.TokenString, jwtAuthResult1.AccessToken, now);
70+
71+
var jwtAuthResult2 = jwtAuthManager.GenerateTokens(userName, claims, now.AddMinutes(-jwtTokenConfig.AccessTokenExpiration - 1));
72+
Assert.ThrowsException<SecurityTokenExpiredException>(() => jwtAuthManager.Refresh(jwtAuthResult2.RefreshToken.TokenString, jwtAuthResult2.AccessToken, now));
73+
}
74+
75+
[TestMethod]
76+
public void ShouldThrowExceptionWhenRefreshTokenIsForged()
77+
{
78+
var jwtAuthManager = _serviceProvider.GetRequiredService<IJwtAuthManager>();
79+
var jwtTokenConfig = _serviceProvider.GetRequiredService<JwtTokenConfig>();
80+
var now = DateTime.Now;
81+
82+
var claims1 = new[]
83+
{
84+
new Claim(ClaimTypes.Name,"admin"),
85+
new Claim(ClaimTypes.Role, UserRoles.Admin)
86+
};
87+
var tokens1 = jwtAuthManager.GenerateTokens("admin", claims1, now.AddMinutes(-jwtTokenConfig.AccessTokenExpiration));
88+
89+
var claims2 = new[]
90+
{
91+
new Claim(ClaimTypes.Name,"test1"),
92+
new Claim(ClaimTypes.Role, UserRoles.Admin)
93+
};
94+
var tokens2 = jwtAuthManager.GenerateTokens("test1", claims2, now.AddMinutes(-jwtTokenConfig.AccessTokenExpiration));
95+
96+
// forge a token: try to use the refresh token for "test1", but use the access token for "admin"
97+
var e = Assert.ThrowsException<SecurityTokenException>(() => jwtAuthManager.Refresh(tokens2.RefreshToken.TokenString, tokens1.AccessToken, now));
7098
Assert.AreEqual("Invalid token", e.Message);
7199
}
72100
}

webapi/JwtAuthDemo.IntegrationTests/ValuesControllerTests.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using JwtAuthDemo.Services;
1313
using Microsoft.AspNetCore.Authentication.JwtBearer;
1414
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.IdentityModel.Tokens;
1516
using Microsoft.VisualStudio.TestTools.UnitTesting;
1617

1718
namespace JwtAuthDemo.IntegrationTests
@@ -64,12 +65,10 @@ public async Task ShouldGetAllKeyValuePairsUsingSuccessLogin()
6465
public async Task ShouldReturn401ForInvalidToken()
6566
{
6667
const string invalidTokenString =
67-
@"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsImV4cCI6MTU5NDg1NzYxNSwiaXNzIjoiaHR0cHM6Ly9teXdlYmFwaS5jb20iLCJhdWQiOiJNeSBXZWJBcGkgVXNlcnMifQ.kjO-4siQxx3JVPVtV_jbmSP5fLp-SIJL92Zq3-weCIg";
68+
@"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYW8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJBZG1pbiIsImV4cCI6MTU5NDg1NzYxNSwiaXNzIjoiaHR0cHM6Ly9teXdlYmFwaS5jb20iLCJhdWQiOiJNeSBXZWJBcGkgVXNlcnMifQ.kjO-4siQxx3JVPVtV_jbmSP5fLp-SIJL92Zq3-weCIg";
6869

6970
var jwtAuthManager = _serviceProvider.GetRequiredService<IJwtAuthManager>();
70-
var (principal, jwtSecurityToken) = jwtAuthManager.DecodeJwtToken(invalidTokenString);
71-
Assert.AreEqual("admin", principal.Identity.Name);
72-
Assert.IsNotNull(jwtSecurityToken);
71+
Assert.ThrowsException<SecurityTokenInvalidSignatureException>(() => jwtAuthManager.DecodeJwtToken(invalidTokenString));
7372

7473
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, invalidTokenString);
7574
var response = await _httpClient.GetAsync("api/values");
@@ -90,7 +89,9 @@ public async Task ShouldReturn401ForExpiredToken()
9089

9190
// expired token
9291
var jwtResult = jwtAuthManager.GenerateTokens(userName, claims, DateTime.Now.AddMinutes(-jwtTokenConfig.AccessTokenExpiration - 1));
93-
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, jwtResult.AccessToken);
92+
var invalidTokenString = jwtResult.AccessToken;
93+
Assert.ThrowsException<SecurityTokenExpiredException>(() => jwtAuthManager.DecodeJwtToken(invalidTokenString));
94+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(JwtBearerDefaults.AuthenticationScheme, invalidTokenString);
9495
var response = await _httpClient.GetAsync("api/values");
9596
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
9697

webapi/JwtAuthDemo/Infrastructure/JwtAuthManager.cs

+7-2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime n
104104

105105
public (ClaimsPrincipal, JwtSecurityToken) DecodeJwtToken(string token)
106106
{
107+
if (string.IsNullOrWhiteSpace(token))
108+
{
109+
throw new SecurityTokenException("Invalid token");
110+
}
107111
var principal = new JwtSecurityTokenHandler()
108112
.ValidateToken(token,
109113
new TokenValidationParameters
@@ -113,8 +117,9 @@ public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime n
113117
ValidateIssuerSigningKey = true,
114118
IssuerSigningKey = new SymmetricSecurityKey(_secret),
115119
ValidAudience = _jwtTokenConfig.Audience,
116-
ValidateAudience = false,
117-
ValidateLifetime = false
120+
ValidateAudience = true,
121+
ValidateLifetime = true,
122+
ClockSkew = TimeSpan.FromMinutes(1)
118123
},
119124
out var validatedToken);
120125
return (principal, validatedToken as JwtSecurityToken);

webapi/JwtAuthDemo/Startup.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void ConfigureServices(IServiceCollection services)
4444
ValidateIssuerSigningKey = true,
4545
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtTokenConfig.Secret)),
4646
ValidAudience = jwtTokenConfig.Audience,
47-
ValidateAudience = false,
47+
ValidateAudience = true,
4848
ValidateLifetime = true,
4949
ClockSkew = TimeSpan.FromMinutes(1)
5050
};
@@ -73,10 +73,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
7373
app.UseDeveloperExceptionPage();
7474
}
7575

76-
//if (Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") != "true")
77-
{
78-
app.UseHttpsRedirection();
79-
}
76+
app.UseHttpsRedirection();
8077

8178
app.UseSwagger();
8279
app.UseSwaggerUI(c =>

webapi/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ This repository demos an ASP.NET Core web API application using JWT auth, and an
1414

1515
```Docker
1616
docker build -t jwtauthdemo_api .
17-
docker run --rm -it jwtauthdemo_api
1817
```
18+
19+
recommend to use the `docker-compose.yml` file in the parent directory to launch the app.

0 commit comments

Comments
 (0)