Skip to content

Commit

Permalink
update to Microsoft.IdentityModel.JsonWebTokens
Browse files Browse the repository at this point in the history
  • Loading branch information
omsmith committed Aug 1, 2024
1 parent 60f10f4 commit c084658
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 89 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<PackageVersion Include="RichardSzalay.MockHttp" Version="6.0.0" />
<PackageVersion Include="SimpleLogInterface" Version="3.0.1" />
<PackageVersion Include="System.Collections.Immutable" Version="6.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.36.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.36.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/D2L.Security.OAuth2/D2L.Security.OAuth2.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<PackageReference Include="D2L.CodeStyle.Annotations" />
<PackageReference Include="D2L.Services.Core.Exceptions" />
<PackageReference Include="System.Collections.Immutable" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net60'">
Expand Down
32 changes: 15 additions & 17 deletions src/D2L.Security.OAuth2/Keys/Default/TokenSigner.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using Microsoft.IdentityModel.JsonWebTokens;
using System.Threading.Tasks;
using D2L.CodeStyle.Annotations;
using D2L.Security.OAuth2.Validation.Exceptions;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;

namespace D2L.Security.OAuth2.Keys.Default {
public sealed partial class TokenSigner : ITokenSigner {

private readonly IPrivateKeyProvider m_privateKeyProvider;
private readonly JsonWebTokenHandler m_tokenHandler = new() { SetDefaultTimesOnTokenCreation = false };

public TokenSigner(
IKeyManagementService keyManagementService
Expand All @@ -22,31 +23,28 @@ IPrivateKeyProvider privateKeyProvider

[GenerateSync]
async Task<string> ITokenSigner.SignAsync( UnsignedToken token ) {
JwtSecurityToken jwt;
using( D2LSecurityToken securityToken = await m_privateKeyProvider
.GetSigningCredentialsAsync()
.ConfigureAwait( false )
) {
jwt = new JwtSecurityToken(
issuer: token.Issuer,
audience: token.Audience,
claims: Enumerable.Empty<Claim>(),
notBefore: token.NotBefore,
expires: token.ExpiresAt,
signingCredentials: securityToken.GetSigningCredentials()
);
SecurityTokenDescriptor jwt = new SecurityTokenDescriptor() {
Issuer = token.Issuer,
Audience = token.Audience,
NotBefore = token.NotBefore,
Expires = token.ExpiresAt,
SigningCredentials = securityToken.GetSigningCredentials(),
Claims = new Dictionary<string, object>(),
};

var claims = token.Claims;
foreach( var claim in claims ) {
if( jwt.Payload.ContainsKey( claim.Key ) ) {
if( jwt.Claims.ContainsKey( claim.Key ) ) {
throw new ValidationException( $"'{claim.Key}' is already part of the payload" );
}
jwt.Payload.Add( claim.Key, claim.Value );
jwt.Claims.Add( claim.Key, claim.Value );
}

var jwtHandler = new JwtSecurityTokenHandler();

string signedRawToken = jwtHandler.WriteToken( jwt );
string signedRawToken = m_tokenHandler.CreateToken( jwt );

return signedRawToken;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.JsonWebTokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
Expand All @@ -10,16 +10,12 @@
using D2L.Services;
using D2L.CodeStyle.Annotations;

#if DNXCORE50
using System.IdentityModel.Tokens.Jwt;
#endif

namespace D2L.Security.OAuth2.Provisioning.Default {
internal sealed partial class CachedAccessTokenProvider : IAccessTokenProvider {
private readonly INonCachingAccessTokenProvider m_accessTokenProvider;
private readonly Uri m_authEndpoint;
private readonly TimeSpan m_tokenRefreshGracePeriod;
private readonly JwtSecurityTokenHandler m_tokenHandler;
private readonly JsonWebTokenHandler m_tokenHandler = new();

public CachedAccessTokenProvider(
INonCachingAccessTokenProvider accessTokenProvider,
Expand All @@ -29,8 +25,6 @@ TimeSpan tokenRefreshGracePeriod
m_accessTokenProvider = accessTokenProvider;
m_authEndpoint = authEndpoint;
m_tokenRefreshGracePeriod = tokenRefreshGracePeriod;

m_tokenHandler = new JwtSecurityTokenHandler();
}

[GenerateSync]
Expand Down
13 changes: 4 additions & 9 deletions src/D2L.Security.OAuth2/Validation/AccessTokens/AccessToken.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.JsonWebTokens;
using System.Security.Claims;
using D2L.CodeStyle.Annotations;
using static D2L.CodeStyle.Annotations.Objects;

#if DNXCORE50
using System.IdentityModel.Tokens.Jwt;
#endif

namespace D2L.Security.OAuth2.Validation.AccessTokens {
[Immutable]
internal sealed class AccessToken : IAccessToken {
[Mutability.Audited( "Todd Lang", "02-Mar-2018", ".Net class we can't modify, but is used immutably." )]
private readonly JwtSecurityToken m_inner;
private readonly JsonWebToken m_inner;
private readonly IAccessToken m_this;

internal AccessToken( JwtSecurityToken jwtSecurityToken ) {
internal AccessToken( JsonWebToken jwtSecurityToken ) {
m_inner = jwtSecurityToken;
m_this = this;
}
Expand All @@ -35,7 +30,7 @@ IEnumerable<Claim> IAccessToken.Claims {
}

string IAccessToken.SensitiveRawAccessToken {
get { return m_inner.RawData; }
get { return m_inner.EncodedToken; }
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
using System;
using System.Collections.Immutable;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Threading;
using Microsoft.IdentityModel.JsonWebTokens;
using System.Threading.Tasks;
using D2L.Security.OAuth2.Keys.Default;
using D2L.Security.OAuth2.Validation.Exceptions;
using D2L.Services;
using D2L.CodeStyle.Annotations;

#if DNXCORE50
using System.IdentityModel.Tokens.Jwt;
#endif

namespace D2L.Security.OAuth2.Validation.AccessTokens {
internal static class JsonWebTokenHandlerExtensions {

public static TokenValidationResult ValidateToken(
this JsonWebTokenHandler @this,
SecurityToken token,
TokenValidationParameters validationParameters
) => @this.ValidateTokenAsync( token, validationParameters ).ConfigureAwait( false ).GetAwaiter().GetResult();

}

internal sealed partial class AccessTokenValidator : IAccessTokenValidator {
internal static readonly ImmutableHashSet<string> ALLOWED_SIGNATURE_ALGORITHMS = ImmutableHashSet.Create(
SecurityAlgorithms.RsaSha256,
Expand All @@ -23,10 +28,7 @@ internal sealed partial class AccessTokenValidator : IAccessTokenValidator {
);

private readonly IPublicKeyProvider m_publicKeyProvider;
private readonly ThreadLocal<JwtSecurityTokenHandler> m_tokenHandler = new ThreadLocal<JwtSecurityTokenHandler>(
valueFactory: () => new JwtSecurityTokenHandler(),
trackAllValues: false
);
private readonly JsonWebTokenHandler m_tokenHandler = new();

public AccessTokenValidator(
IPublicKeyProvider publicKeyProvider
Expand All @@ -41,31 +43,27 @@ IPublicKeyProvider publicKeyProvider
async Task<IAccessToken> IAccessTokenValidator.ValidateAsync(
string token
) {
var tokenHandler = m_tokenHandler.Value;

if( !tokenHandler.CanReadToken( token ) ) {
if( !m_tokenHandler.CanReadToken( token ) ) {
throw new ValidationException( "Couldn't parse token" );
}

var unvalidatedToken = ( JwtSecurityToken )tokenHandler.ReadToken(
var unvalidatedToken = ( JsonWebToken )m_tokenHandler.ReadToken(
token
);

if( !ALLOWED_SIGNATURE_ALGORITHMS.Contains( unvalidatedToken.SignatureAlgorithm ) ) {
if( !ALLOWED_SIGNATURE_ALGORITHMS.Contains( unvalidatedToken.Alg ) ) {
string message = string.Format(
"Signature algorithm '{0}' is not supported. Permitted algorithms are '{1}'",
unvalidatedToken.SignatureAlgorithm,
unvalidatedToken.Alg,
string.Join( ",", ALLOWED_SIGNATURE_ALGORITHMS )
);
throw new InvalidTokenException( message );
}

if( !unvalidatedToken.Header.ContainsKey( "kid" ) ) {
if( !unvalidatedToken.TryGetHeaderValue( "kid", out string keyId ) ) {
throw new InvalidTokenException( "KeyId not found in token" );
}

string keyId = unvalidatedToken.Header[ "kid" ].ToString();

using D2LSecurityToken signingKey = ( await m_publicKeyProvider
.GetByIdAsync( keyId )
.ConfigureAwait( false )
Expand All @@ -82,12 +80,14 @@ string token
IAccessToken accessToken;

try {
tokenHandler.ValidateToken(
token,
validationParameters,
out SecurityToken securityToken
);
accessToken = new AccessToken( ( JwtSecurityToken )securityToken );
TokenValidationResult validationResult = await m_tokenHandler.ValidateTokenAsync(
unvalidatedToken,
validationParameters
).ConfigureAwait( false );
if( !validationResult.IsValid ) {
throw validationResult.Exception;
}
accessToken = new AccessToken( (JsonWebToken)validationResult.SecurityToken );
} catch( SecurityTokenExpiredException e ) {
throw new ExpiredTokenException( e );
} catch( SecurityTokenNotYetValidException e ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'net60'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageReference Include="Moq" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Threading;
using D2L.Services;
using NUnit.Framework;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.JsonWebTokens;

namespace D2L.Security.OAuth2.Keys.Default {
[TestFixture]
Expand Down Expand Up @@ -82,13 +82,13 @@ int threadNumber
}

private static string Sign( D2LSecurityToken securityToken ) {
JwtSecurityToken jwt = new JwtSecurityToken(
issuer: TEST_ISSUER,
signingCredentials: securityToken.GetSigningCredentials()
);
SecurityTokenDescriptor jwt = new() {
Issuer = TEST_ISSUER,
SigningCredentials = securityToken.GetSigningCredentials()
};

JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
string signedToken = jwtHandler.WriteToken( jwt );
JsonWebTokenHandler jwtHandler = new();
string signedToken = jwtHandler.CreateToken( jwt );

return signedToken;
}
Expand All @@ -97,21 +97,23 @@ private static void AssertSignatureVerifiable(
D2LSecurityToken securityToken,
string signedToken
) {
JwtSecurityTokenHandler validationTokenHandler = new JwtSecurityTokenHandler();
JsonWebTokenHandler validationTokenHandler = new();
TokenValidationParameters validationParameters = new TokenValidationParameters() {
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
RequireSignedTokens = true,
IssuerSigningKey = securityToken
};
validationTokenHandler.ValidateToken(
TokenValidationResult validationResult = validationTokenHandler.ValidateToken(
signedToken,
validationParameters,
out SecurityToken validatedToken
validationParameters
);
if( !validationResult.IsValid ) {
throw validationResult.Exception;
}

JwtSecurityToken validatedJwt = validatedToken as JwtSecurityToken;
JsonWebToken validatedJwt = validationResult.SecurityToken as JsonWebToken;
Assert.AreEqual( TEST_ISSUER, validatedJwt.Issuer );
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.JsonWebTokens;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
Expand All @@ -25,16 +25,16 @@ private static class TestData {
private IPublicKeyDataProvider m_publicKeyDataProvider;
private ITokenSigner m_tokenSigner;
private INonCachingAccessTokenProvider m_accessTokenProvider;
private JwtSecurityToken m_actualAssertion;
private JsonWebToken m_actualAssertion;

[SetUp]
public void SetUp() {
Mock<IAuthServiceClient> clientMock = new Mock<IAuthServiceClient>();
clientMock
.Setup( x => x.ProvisionAccessTokenAsync( It.IsAny<string>(), It.IsAny<IEnumerable<Scope>>() ) )
.Callback<string, IEnumerable<Scope>>( ( assertion, _ ) => {
var tokenHandler = new JwtSecurityTokenHandler();
m_actualAssertion = ( JwtSecurityToken )tokenHandler.ReadToken( assertion );
var tokenHandler = new JsonWebTokenHandler();
m_actualAssertion = ( JsonWebToken )tokenHandler.ReadToken( assertion );
} )
.ReturnsAsync( value: null );

Expand Down Expand Up @@ -63,7 +63,7 @@ await m_accessTokenProvider
var publicKeys = ( await m_publicKeyDataProvider.GetAllAsync().ConfigureAwait( false ) ).ToList();

string expectedKeyId = publicKeys.First().Id.ToString();
string actualKeyId = m_actualAssertion.Header.Kid;
string actualKeyId = m_actualAssertion.GetHeaderValue<string>( "kid" );

Assert.AreEqual( 1, publicKeys.Count );
Assert.AreEqual( expectedKeyId, actualKeyId );
Expand All @@ -90,7 +90,7 @@ await m_accessTokenProvider
AssertClaimEquals( m_actualAssertion, Constants.Claims.USER_ID, TestData.USER );
}

private void AssertClaimEquals( JwtSecurityToken token, string name, string value ) {
private void AssertClaimEquals( JsonWebToken token, string name, string value ) {
Claim claim = token.Claims.FirstOrDefault( c => c.Type == name );
Assert.IsNotNull( claim );
Assert.AreEqual( value, claim.Value );
Expand Down
Loading

0 comments on commit c084658

Please sign in to comment.