forked from davidfowl/TodoApp
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Made tests generate JWTs using the same key material.
- Loading branch information
Showing
15 changed files
with
388 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -295,3 +295,5 @@ __pycache__/ | |
|
||
# MacOS | ||
.DS_Store | ||
*.db-shm | ||
*.db-wal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using System.Globalization; | ||
using System.IdentityModel.Tokens.Jwt; | ||
using System.Security.Claims; | ||
using System.Security.Principal; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace TodoApi.Tests; | ||
|
||
// Lifted from: https://github.com/dotnet/aspnetcore/blob/b39a258cbce1b16ee98679ef7d2ddc2e09040a6b/src/Tools/dotnet-user-jwts/src/Helpers/JwtIssuer.cs#L13 | ||
internal sealed class JwtIssuer | ||
{ | ||
private readonly SymmetricSecurityKey _signingKey; | ||
|
||
public JwtIssuer(string issuer, byte[] signingKeyMaterial) | ||
{ | ||
Issuer = issuer; | ||
_signingKey = new SymmetricSecurityKey(signingKeyMaterial); | ||
} | ||
|
||
public string Issuer { get; } | ||
|
||
public JwtSecurityToken Create(JwtCreatorOptions options) | ||
{ | ||
var identity = new GenericIdentity(options.Name); | ||
|
||
identity.AddClaim(new Claim(JwtRegisteredClaimNames.Sub, options.Name)); | ||
|
||
var id = Guid.NewGuid().ToString().GetHashCode().ToString("x", CultureInfo.InvariantCulture); | ||
identity.AddClaim(new Claim(JwtRegisteredClaimNames.Jti, id)); | ||
|
||
if (options.Scopes is { } scopesToAdd) | ||
{ | ||
identity.AddClaims(scopesToAdd.Select(s => new Claim("scope", s))); | ||
} | ||
|
||
if (options.Roles is { } rolesToAdd) | ||
{ | ||
identity.AddClaims(rolesToAdd.Select(r => new Claim(ClaimTypes.Role, r))); | ||
} | ||
|
||
if (options.Claims is { Count: > 0 } claimsToAdd) | ||
{ | ||
identity.AddClaims(claimsToAdd.Select(kvp => new Claim(kvp.Key, kvp.Value))); | ||
} | ||
|
||
// Although the JwtPayload supports having multiple audiences registered, the | ||
// creator methods and constructors don't provide a way of setting multiple | ||
// audiences. Instead, we have to register an `aud` claim for each audience | ||
// we want to add so that the multiple audiences are populated correctly. | ||
if (options.Audiences is { Count: > 0 } audiences) | ||
{ | ||
identity.AddClaims(audiences.Select(aud => new Claim(JwtRegisteredClaimNames.Aud, aud))); | ||
} | ||
|
||
var handler = new JwtSecurityTokenHandler(); | ||
var jwtSigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256Signature); | ||
var jwtToken = handler.CreateJwtSecurityToken(Issuer, audience: null, identity, options.NotBefore, options.ExpiresOn, issuedAt: DateTime.UtcNow, jwtSigningCredentials); | ||
return jwtToken; | ||
} | ||
|
||
public static string WriteToken(JwtSecurityToken token) | ||
{ | ||
var handler = new JwtSecurityTokenHandler(); | ||
return handler.WriteToken(token); | ||
} | ||
|
||
public static JwtSecurityToken Extract(string token) => new JwtSecurityToken(token); | ||
|
||
public bool IsValid(string encodedToken) | ||
{ | ||
var handler = new JwtSecurityTokenHandler(); | ||
var tokenValidationParameters = new TokenValidationParameters | ||
{ | ||
IssuerSigningKey = _signingKey, | ||
ValidateAudience = false, | ||
ValidateIssuer = false, | ||
ValidateIssuerSigningKey = true | ||
}; | ||
if (handler.ValidateToken(encodedToken, tokenValidationParameters, out _).Identity?.IsAuthenticated == true) | ||
{ | ||
return true; | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
internal sealed record JwtCreatorOptions( | ||
string Scheme, | ||
string Name, | ||
List<string> Audiences, | ||
string Issuer, | ||
DateTime NotBefore, | ||
DateTime ExpiresOn, | ||
List<string> Roles, | ||
List<string> Scopes, | ||
Dictionary<string, string> Claims); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using Microsoft.EntityFrameworkCore.Migrations; | ||
|
||
#nullable disable | ||
|
||
namespace Sample.Migrations | ||
{ | ||
/// <inheritdoc /> | ||
public partial class Owners : Migration | ||
{ | ||
/// <inheritdoc /> | ||
protected override void Up(MigrationBuilder migrationBuilder) | ||
{ | ||
migrationBuilder.AddColumn<string>( | ||
name: "OwnerId", | ||
table: "Todos", | ||
type: "TEXT", | ||
nullable: false, | ||
defaultValue: ""); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void Down(MigrationBuilder migrationBuilder) | ||
{ | ||
migrationBuilder.DropColumn( | ||
name: "OwnerId", | ||
table: "Todos"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Microsoft.AspNetCore.Authentication.JwtBearer; | ||
using Microsoft.OpenApi.Models; | ||
|
||
namespace TodoApi; | ||
|
||
public static class OpenApiExtensions | ||
{ | ||
public static IEndpointConventionBuilder AddOpenApiSecurityRequirement(this IEndpointConventionBuilder builder) | ||
{ | ||
var scheme = new OpenApiSecurityScheme() | ||
{ | ||
Type = SecuritySchemeType.Http, | ||
Name = JwtBearerDefaults.AuthenticationScheme, | ||
Scheme = JwtBearerDefaults.AuthenticationScheme, | ||
Reference = new() | ||
{ | ||
Type = ReferenceType.SecurityScheme, | ||
Id = JwtBearerDefaults.AuthenticationScheme | ||
} | ||
}; | ||
|
||
return builder.WithOpenApi(operation => new(operation) | ||
{ | ||
Security = | ||
{ | ||
new() | ||
{ | ||
[scheme] = new List<string>() | ||
} | ||
} | ||
}); | ||
} | ||
|
||
} |
Oops, something went wrong.