Skip to content
Merged
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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
dotnet-version: |
6.x
8.x
9.x

- name: Restore
working-directory: ./src
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@

This is a .NET implementation of the JWT library for the NATS ecosystem.

> [!CAUTION]
> ### Very Important Disclaimer
>
> This repository provides an API to build NATS JWTs using .NET. However, at
> this time it is _not_ a supported API. Use at your own risk.
>
> One important take away from this project is that the purpose of the library is
> for building JWTs, not to validate them exhaustively. This means that tokens
> generated by this library are expected to be validated by a process that uses
> the [NATS JWT Go library](github.com/nats-io/jwt). As that library is the one
> used by:
>
> - [nats-server](github.com/nats-io/nats-server),
> - [nats-account-server](github.com/nats-io/nats-account-server)
> - [nsc](github.com/nats-io/nsc)
>
> Under that context, ultimate validity of the JWT is delegated to tools or
> servers that use the [NATS JWT Go library](github.com/nats-io/jwt). Use of this
> library implies an agreement with the above disclaimer.

## TODO

- [x] Add public API analyzer
Expand Down
2 changes: 1 addition & 1 deletion src/NATS.Jwt.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F3E7EFAD-B3E
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{79B0CD3B-C106-44A2-9A2A-CFDA69A3016A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".", ".", "{1F24478C-D5CB-4A58-A74E-6371F7F95C01}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1F24478C-D5CB-4A58-A74E-6371F7F95C01}"
ProjectSection(SolutionItems) = preProject
README.md = ..\README.md
LICENSE = ..\LICENSE
Expand Down
25 changes: 7 additions & 18 deletions src/NATS.Jwt/EncodingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using System;
using System.Text;

namespace NATS.Jwt
{
Expand Down Expand Up @@ -33,26 +32,16 @@ public static string ToBase64UrlEncoded(byte[] bytes)
/// </summary>
/// <param name="encodedString">The base64 URL-encoded string to decode.</param>
/// <returns>The decoded string.</returns>
public static string FromBase64UrlEncoded(string encodedString)
public static byte[] FromBase64UrlEncoded(string encodedString)
{
string replace = encodedString.Replace("_", "/").Replace("-", "+");
try
string base64 = encodedString.Replace("_", "/").Replace("-", "+");
switch (base64.Length % 4)
{
return Encoding.ASCII.GetString(Convert.FromBase64String(replace));
}
catch (FormatException)
{
// maybe wasn't padded correctly?
try
{
return Encoding.ASCII.GetString(Convert.FromBase64String(replace + "="));
}
catch (FormatException)
{
// maybe wasn't padded correctly?
return Encoding.ASCII.GetString(Convert.FromBase64String(replace + "=="));
}
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}

return Convert.FromBase64String(base64);
}
}
}
61 changes: 61 additions & 0 deletions src/NATS.Jwt/ModelValidationExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) The NATS Authors.
// Licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using NATS.Jwt.Models;
using NATS.NKeys;

namespace NATS.Jwt;

/// <summary>
/// Provides extension methods for validating JSON Web Token (JWT) models.
/// </summary>
public static class ModelValidationExtensions
{
private static readonly Dictionary<Type, PrefixByte[]> ExpectedClaimsPrefixes = new()
{
{ typeof(NatsAccountClaims), [PrefixByte.Account, PrefixByte.Operator] },
{ typeof(NatsActivationClaims), [PrefixByte.Account, PrefixByte.Operator] },
{ typeof(NatsAuthorizationRequestClaims), [PrefixByte.Server] },
{ typeof(NatsAuthorizationResponseClaims), [PrefixByte.Account] },
{ typeof(NatsGenericClaims), [] },
{ typeof(NatsOperatorClaims), [PrefixByte.Operator] },
{ typeof(NatsUserClaims), [PrefixByte.Account] },
};

/// <summary>
/// Determines the expected prefix bytes for the given JWT claims data.
/// </summary>
/// <param name="claims">The JWT claims data whose expected prefixes need to be determined.</param>
/// <returns>An array of PrefixByte values indicating the expected prefixes for the specified claims.</returns>
/// <exception cref="NatsJwtException">Thrown if the expected prefixes cannot be determined for the given claims type.</exception>
public static PrefixByte[] ExpectedPrefixes(this JwtClaimsData claims)
{
if (!ExpectedClaimsPrefixes.TryGetValue(claims.GetType(), out var prefixes))
{
throw new NatsJwtException($"Can't find prefixes for {claims.GetType().Name}");
}

return prefixes;
}

/// <summary>
/// Validates the specified JWT header to ensure its type and algorithm are supported.
/// </summary>
/// <param name="header">The JWT header to validate.</param>
/// <exception cref="NatsJwtException">Invalid JWT header.</exception>
public static void Validate(this JwtHeader header)
{
if (!string.Equals("JWT", header.Type, StringComparison.InvariantCultureIgnoreCase))
{
throw new NatsJwtException($"Invalid JWT header: not supported type {header.Type}");
}

if (!string.Equals("ed25519", header.Algorithm, StringComparison.InvariantCultureIgnoreCase)
&& !string.Equals(NatsJwt.AlgorithmNkey, header.Algorithm, StringComparison.InvariantCultureIgnoreCase))
{
throw new NatsJwtException($"Invalid JWT header: unexpected {header.Algorithm} algorithm");
}
}
}
2 changes: 1 addition & 1 deletion src/NATS.Jwt/NATS.Jwt.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="NATS.NKeys" Version="1.0.0-preview.3" />
<PackageReference Include="System.Text.Json" Version="8.0.5"/>
<PackageReference Include="NATS.NKeys" Version="1.0.0-preview.1"/>
<PackageReference Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" ExcludeAssets="Compile" PrivateAssets="All"/>
</ItemGroup>

Expand Down
Loading