Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update to Microsoft.IdentityModel.JsonWebTokens #370

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading