Skip to content

Commit bc794fc

Browse files
authored
Remove System.IdentityModel.Tokens.Jwt dependency (#1862)
* Internally decode JWT. * Remove System.IdentityModel.Tokens.Jwt dependency * Update tests
1 parent 345573f commit bc794fc

File tree

7 files changed

+55
-36
lines changed

7 files changed

+55
-36
lines changed

src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
<PackageReference Include="Microsoft.Graph.Core" Version="2.0.15" />
1717
<PackageReference Include="Microsoft.Identity.Client" Version="4.50.0" />
1818
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="2.26.0" />
19-
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.27.0" />
20-
<PackageReference Include="Microsoft.IdentityModel.Logging" Version="6.27.0" />
21-
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.27.0" />
22-
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
2319
<!--Always ensure this version matches the versions of System.Security.Cryptography.* dependencies of Microsoft.Identity.Client when targeting PowerShell 6 and below.-->
2420
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="7.0.1" />
2521
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// ------------------------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
3+
// ------------------------------------------------------------------------------
4+
5+
namespace Microsoft.Graph.PowerShell.Authentication.Core.Models
6+
{
7+
internal class JwtContent
8+
{
9+
public string Header { get; set; }
10+
public string Payload { get; set; }
11+
}
12+
}

src/Authentication/Authentication.Core/Utilities/JwtHelpers.cs

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
33
// ------------------------------------------------------------------------------
44

5+
using Microsoft.Graph.PowerShell.Authentication.Core.Models;
6+
using Microsoft.Graph.PowerShell.Authentication.Models;
57
using Microsoft.Identity.Client;
68
using Newtonsoft.Json;
79
using System;
810
using System.Globalization;
9-
using System.IdentityModel.Tokens.Jwt;
11+
using System.Text;
1012

1113
namespace Microsoft.Graph.PowerShell.Authentication.Core.Utilities
1214
{
@@ -23,7 +25,7 @@ internal static class JwtHelpers
2325
/// <param name="authContext">An <see cref="IAuthContext"/> to store JWT claims in.</param>
2426
internal static void DecodeJWT(string jwToken, IAccount account, ref IAuthContext authContext)
2527
{
26-
var jwtPayload = DecodeToObject<Models.JwtPayload>(jwToken);
28+
var jwtPayload = DecodeToObject<JwtPayload>(jwToken);
2729
if (authContext.AuthType == AuthenticationType.UserProvidedAccessToken)
2830
{
2931
if (jwtPayload == null)
@@ -50,22 +52,6 @@ internal static void DecodeJWT(string jwToken, IAccount account, ref IAuthContex
5052
authContext.Account = jwtPayload?.Upn ?? account?.Username;
5153
}
5254

53-
/// <summary>
54-
/// Decodes a JWT token by extracting claims from the payload.
55-
/// </summary>
56-
/// <param name="jwToken">A JWT string.</param>
57-
internal static string Decode(string jwToken)
58-
{
59-
JwtSecurityTokenHandler jwtHandler = new JwtSecurityTokenHandler();
60-
if (jwtHandler.CanReadToken(jwToken))
61-
{
62-
JwtSecurityToken token = jwtHandler.ReadJwtToken(jwToken);
63-
return token.Payload.SerializeToJson();
64-
} else {
65-
return null;
66-
}
67-
}
68-
6955
/// <summary>
7056
/// Decodes a JWT token by extracting claims from the payload to an object of type T.
7157
/// </summary>
@@ -75,17 +61,49 @@ internal static T DecodeToObject<T>(string jwtString)
7561
{
7662
try
7763
{
78-
string decodedJWT = Decode(jwtString);
79-
if (decodedJWT == null)
64+
var decodedJWT = DecodeJWT(jwtString);
65+
if (string.IsNullOrWhiteSpace(decodedJWT?.Payload))
8066
return default;
81-
return JsonConvert.DeserializeObject<T>(decodedJWT);
67+
return JsonConvert.DeserializeObject<T>(decodedJWT.Payload);
8268
}
8369
catch (Exception ex)
8470
{
8571
throw new AuthenticationException(ErrorConstants.Message.InvalidJWT, ex);
8672
}
8773
}
8874

75+
internal static JwtContent DecodeJWT(string jwtString)
76+
{
77+
// See https://tools.ietf.org/html/rfc7519
78+
if (string.IsNullOrWhiteSpace(jwtString) || !jwtString.Contains(".") || !jwtString.StartsWith("eyJ"))
79+
throw new ArgumentException("Invalid JSON Web Token (JWT).");
80+
81+
var jwtSegments = jwtString.Split('.');
82+
83+
if (jwtSegments.Length <= 1)
84+
throw new ArgumentException("Invalid JWT. JWT does not have a payload.");
85+
86+
// Header
87+
var jwtHeader = DecodeJwtSegment(jwtSegments[0]);
88+
89+
// Payload
90+
var jwtPayload = DecodeJwtSegment(jwtSegments[1]);
91+
92+
return new JwtContent { Header = jwtHeader, Payload = jwtPayload };
93+
}
94+
95+
private static string DecodeJwtSegment(string jwtSegment)
96+
{
97+
jwtSegment = jwtSegment.Replace('-', '+').Replace('_', '/');
98+
// Fixes padding by adding '=' until header length modulus 4 equals 0.
99+
while ((jwtSegment.Length % 4) != 0)
100+
jwtSegment += "=";
101+
102+
var jwtTokenBytes = Convert.FromBase64String(jwtSegment);
103+
var jwtSegmentInJson = Encoding.UTF8.GetString(jwtTokenBytes);
104+
return jwtSegmentInJson;
105+
}
106+
89107
/// <summary>
90108
/// Converts a DateTime to Unix timestamp in seconds past epoch.
91109
/// </summary>

src/Authentication/Authentication.Core/Utilities/UserProvidedTokenCredential.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// ------------------------------------------------------------------------------
44

55
using Azure.Core;
6+
using Microsoft.Graph.PowerShell.Authentication.Models;
67
using System;
78
using System.Text;
89
using System.Threading;
@@ -16,7 +17,7 @@ public override AccessToken GetToken(TokenRequestContext requestContext, Cancell
1617
{
1718
cancellationToken.ThrowIfCancellationRequested();
1819
var token = Encoding.UTF8.GetString(GraphSession.Instance.InMemoryTokenCache.ReadTokenData());
19-
var jwtPayload = JwtHelpers.DecodeToObject<Models.JwtPayload>(token);
20+
var jwtPayload = JwtHelpers.DecodeToObject<JwtPayload>(token);
2021
var exp = jwtPayload?.Exp == null ? DateTimeOffset.Now.AddMinutes(55) : DateTimeOffset.FromUnixTimeSeconds(jwtPayload.Exp);
2122
return new AccessToken(token, exp);
2223
}

src/Authentication/Authentication.Test/Helpers/AuthenticationHelpersTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public AuthenticationHelpersTests()
3232
public async Task ShouldUseDelegateAuthProviderWhenUserAccessTokenIsProvidedAsync()
3333
{
3434
// Arrange
35-
string accessToken = "ACCESS_TOKEN_VIA_DELEGATE_PROVIDER";
35+
string accessToken = "eyJhbGciOiJIUzI1NiJ9.eyJSb2xlIjoiVGVzdCIsIklzc3VlciI6Iklzc3VlciIsIlVzZXJuYW1lIjoiVGVzdCIsImV4cCI6MTY3ODQ4ODgxNiwiaWF0IjoxNjc4NDg4ODE2fQ.hpYypwHAV8H3jb4KuTiLpgLWy9A8H2d9HG7SxJ8Kpn0";
3636
GraphSession.Instance.InMemoryTokenCache = new InMemoryTokenCache(Encoding.UTF8.GetBytes(accessToken));
3737
AuthContext userProvidedAuthContext = new AuthContext
3838
{

src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package>
33
<metadata>
4-
<version>2.0.0-preview2</version>
4+
<version>2.0.0-preview5</version>
55
<id>Microsoft.Graph.Authentication</id>
66
<description>Microsoft Graph PowerShell authentication module</description>
77
<authors>Microsoft</authors>
@@ -29,11 +29,7 @@
2929
<file src="artifacts\Dependencies\Newtonsoft.Json.dll" target="Dependencies" />
3030
<file src="artifacts\Dependencies\Microsoft.Bcl.AsyncInterfaces.dll" target="Dependencies" />
3131
<file src="artifacts\Dependencies\Microsoft.IdentityModel.Abstractions.dll" target="Dependencies" />
32-
<file src="artifacts\Dependencies\Microsoft.IdentityModel.JsonWebTokens.dll" target="Dependencies" />
3332
<file src="artifacts\Dependencies\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies" />
34-
<file src="artifacts\Dependencies\Microsoft.IdentityModel.Tokens.dll" target="Dependencies" />
35-
<file src="artifacts\Dependencies\Microsoft.IdentityModel.Logging.dll" target="Dependencies" />
36-
<file src="artifacts\Dependencies\System.IdentityModel.Tokens.Jwt.dll" target="Dependencies" />
3733
<file src="artifacts\Dependencies\System.Memory.dll" target="Dependencies" />
3834
<file src="artifacts\Dependencies\System.Threading.Tasks.Extensions.dll" target="Dependencies" />
3935
<file src="artifacts\Dependencies\System.Diagnostics.DiagnosticSource.dll" target="Dependencies" />

src/Authentication/Authentication/Utilities/DependencyAssemblyResolver.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public static class DependencyAssemblyResolver
2121
{ "Microsoft.Identity.Client", new Version("4.50.0") },
2222
{ "Microsoft.Identity.Client.Extensions.Msal", new Version("2.26.0") },
2323
{ "Microsoft.IdentityModel.Abstractions", new Version("6.27.0") },
24-
{ "Microsoft.IdentityModel.JsonWebTokens", new Version("6.27.0") },
25-
{ "Microsoft.IdentityModel.Logging", new Version("6.27.0") },
26-
{ "Microsoft.IdentityModel.Tokens", new Version("6.27.0") },
27-
{ "System.IdentityModel.Tokens.Jwt", new Version("6.27.0") },
2824
{ "System.Security.Cryptography.ProtectedData", new Version("7.0.1") },
2925
{ "Newtonsoft.Json", new Version("13.0.2") },
3026
{ "System.Text.Json", new Version("7.0.2") },

0 commit comments

Comments
 (0)