Skip to content

Commit

Permalink
Merge branch 'main' into alex/system-text-1
Browse files Browse the repository at this point in the history
  • Loading branch information
hartmark authored Jun 6, 2022
2 parents 231a40b + a898f88 commit 1fad5d1
Show file tree
Hide file tree
Showing 29 changed files with 471 additions and 403 deletions.
126 changes: 37 additions & 89 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ This library supports generating and decoding [JSON Web Tokens](https://tools.ie
- .NET Framework 4.0 - 4.8
- .NET Standard 1.3
- .NET Standard 2.0
- .NET 5.0
- .NET 6.0

## Jwt.NET
Expand All @@ -47,52 +46,47 @@ var payload = new Dictionary<string, object>
{ "claim1", 0 },
{ "claim2", "claim2-value" }
};
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";

IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJwtAlgorithm algorithm = new RS256Algorithm(certificate);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);

var token = encoder.Encode(payload, secret);
var token = encoder.Encode(payload);
Console.WriteLine(token);
```

#### Or using the fluent builder API

```c#
var token = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(secret)
.WithAlgorithm(new RS256Algorithm(certificate))
.AddClaim("exp", DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds())
.AddClaim("claim1", 0)
.AddClaim("claim2", "claim2-value")
.Encode();

Console.WriteLine(token);
```

The output would be:

>eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s
### Parsing (decoding) and verifying token

```c#
const string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjbGFpbTEiOjAsImNsYWltMiI6ImNsYWltMi12YWx1ZSJ9.8pwBI_HtXqI3UgQHQ_rDRnSQRxFL1SR8fbQoS-5kM5s";
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";

try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJwtAlgorithm algorithm = new RS256Algorithm(certificate);
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);

var json = decoder.Decode(token, secret, verify: true);
var json = decoder.Decode(token);
Console.WriteLine(json);
}
catch (TokenNotYetValidException)
{
Console.WriteLine("Token is not valid yet");
}
catch (TokenExpiredException)
{
Console.WriteLine("Token has expired");
Expand All @@ -107,18 +101,7 @@ catch (SignatureVerificationException)

```c#
var json = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithSecret(secret)
.MustVerifySignature()
.Decode(token);
Console.WriteLine(json);
```

or

```c#
var json = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(certificate)) // asymmetric
.WithAlgorithm(new RS256Algorithm(certificate))
.MustVerifySignature()
.Decode(token);
Console.WriteLine(json);
Expand All @@ -132,58 +115,43 @@ You can also deserialize the JSON payload directly to a .NET type:

```c#
var payload = decoder.DecodeToObject<IDictionary<string, object>>(token, secret);
Console.WriteLine(payload["claim2"]);
```
```

#### Or using the fluent builder API

```c#
var payload = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm()) // symmetric
.WithAlgorithm(new RS256Algorithm(certificate))
        .WithSecret(secret)
.MustVerifySignature()
.Decode<IDictionary<string, object>>(token);
Console.WriteLine(payload["claim2"]);
```

and

```c#
var payload = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(certificate)) // asymmetric
.MustVerifySignature()
.Decode<IDictionary<string, object>>(token);
Console.WriteLine(payload["claim2"]);
```

The output would be:

>claim2-value
### Set and validate token expiration

As described in the [JWT RFC](https://tools.ietf.org/html/rfc7519#section-4.1.4):
As described in the [RFC 7519 section 4.1.4](https://tools.ietf.org/html/rfc7519#section-4.1.4):

>The `exp` claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.
If it is present in the payload and is prior to the current time the token will fail verification. The value must be specified as the number of seconds since the [Unix epoch](https://en.wikipedia.org/wiki/Unix_time), 1/1/1970 UTC.

```c#
IDateTimeProvider provider = new UtcDateTimeProvider();
var now = provider.GetNow();
var now = provider.GetNow().AddMinutes(-5); // token has expired 5 minutes ago
double secondsSinceEpoch = UnixEpoch.GetSecondsSince(now);
double secondsSinceEpoch = UnixEpoch.GetSecondsSince(now);

var payload = new Dictionary<string, object>
{
{ "exp", secondsSinceEpoch }
};
const string secret = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";
var token = encoder.Encode(payload, secret);
var token = encoder.Encode(payload);

var json = decoder.Decode(token, secret, validate: true); // throws TokenExpiredException
decoder.Decode(token); // throws TokenExpiredException
```

Similarly, the `nbf` claim can be used to validate the token is not valid yet, as described in [RFC 7519 section 4.1.5](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5).

### Parsing (decoding) token header

```c#
Expand Down Expand Up @@ -211,32 +179,34 @@ var kid = header.KeyId; // CFAEAE2D650A6CA9862575DE54371EA980643849

### Turning off parts of token validation

If you wish to validate a token but ignore certain parts of the validation (such as the lifetime of the token when refreshing the token), you can pass a `ValidateParameters` object to the constructor of the `JwtValidator` class.
If you'd like to validate a token but ignore certain parts of the validation (such as whether to the token has expired or not valid yet), you can pass a `ValidateParameters` object to the constructor of the `JwtValidator` class.

```c#
var validationParameters = new ValidationParameters
{
ValidateSignature = true,
ValidateExpirationTime = true,
ValidateIssuedTime = true
ValidateExpirationTime = false,
ValidateIssuedTime = false,
TimeMargin = 100
};
IJwtValidator validator = new JwtValidator(serializer, provider, validationParameters);
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
var json = decoder.Decode(expiredToken, secret, verify: true); // will not throw because of expired token
var json = decoder.Decode(expiredToken); // will not throw because of expired token
```

#### Or using the fluent builder API

```c#
var json = JwtBuilder.Create()
.WithAlgorithm(new HMACSHA256Algorithm())
.WithAlgorithm(new RS256Algorirhm(certificate))
.WithSecret(secret)
.WithValidationParameters(
new ValidationParameters
{
ValidateSignature = true,
ValidateExpirationTime = true,
ValidateIssuedTime = true
ValidateExpirationTime = false,
ValidateIssuedTime = false,
TimeMargin = 100
})
.Decode(expiredToken);
```
Expand All @@ -263,7 +233,7 @@ public sealed class CustomJsonSerializer : IJsonSerializer
And then pass this serializer to JwtEncoder constructor:

```c#
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); // symmetric
IJwtAlgorithm algorithm = new RS256Algorirhm(certificate);
IJsonSerializer serializer = new CustomJsonSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
Expand Down Expand Up @@ -308,19 +278,19 @@ public void ConfigureServices(IServiceCollection services)
})
.AddJwt(options =>
{
// secrets, required only for symmetric algorithms
options.Keys = new[] { "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk" };
// secrets, required only for symmetric algorithms, such as HMACSHA256Algorithm
// options.Keys = new[] { "mySecret" };
// force JwtDecoder to throw exception if JWT signature is invalid
options.VerifySignature = true;
// optionally; disable throwing an exception if JWT signature is invalid
// options.VerifySignature = false;
});
// the non-generic version AddJwt() requires you to register an instance of IAlgorithmFactory manually
// the non-generic version AddJwt() requires registering an instance of IAlgorithmFactory manually
services.AddSingleton<IAlgorithmFactory>(new RSAlgorithmFactory(certificate));
// or
services.AddSingleton<IAlgorithmFactory>(new DelegateAlgorithmFactory(algorithm));

// or use the generic version AddJwt<TFactory() if you have a custom implementation of IAlgorithmFactory
// AddJwt<MyCustomAlgorithmFactory(options => ...);
// or use the generic version AddJwt<TFactory() to use a custom implementation of IAlgorithmFactory
.AddJwt<MyCustomAlgorithmFactory(options => ...);
}

public void Configure(IApplicationBuilder app)
Expand All @@ -336,26 +306,6 @@ services.AddSingleton<IIdentityFactory, CustomIdentityFctory>();
services.AddSingleton<ITicketFactory, CustomTicketFactory>();
```

### Register authentication handler to validate JWT

```c#
services.AddAuthentication(options =>
{
// Prevents from System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.
options.DefaultAuthenticateScheme = JwtAuthenticationDefaults.AuthenticationScheme;

// Prevents from System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found.
options.DefaultChallengeScheme = JwtAuthenticationDefaults.AuthenticationScheme;
})
.AddJwt(options =>
{
options.Keys = configureOptions.Keys;
options.VerifySignature = configureOptions.VerifySignature;

// optionally customize
});
```

## License

The following projects and their resulting packages are licensed under Public Domain, see the [LICENSE#Public-Domain](LICENSE.md#Public-Domain) file.
Expand All @@ -366,5 +316,3 @@ The following projects and their resulting packages are licensed under the MIT L

- JWT.Extensions.AspNetCore
- JWT.Extensions.DependencyInjection

**Note:** work in progress as the scenario/usage is not designed yet. The registered component will do nothing but throw an exception.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageTags>jwt;json;asp.net;asp.net core;.net core;authorization</PackageTags>
<PackageLicense>MIT</PackageLicense>
<Version>9.1.0</Version>
<FileVersion>9.0.0.0</FileVersion>
<FileVersion>9.1.0.0</FileVersion>
<AssemblyVersion>9.0.0.0</AssemblyVersion>
<RootNamespace>JWT.Extensions.AspNetCore</RootNamespace>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Authors>Alexander Batishchev</Authors>
<PackageTags>jwt;json;asp.net;asp.net core;.net core;authorization;dependenсy injection</PackageTags>
<PackageLicense>MIT</PackageLicense>
<Version>2.1.0-beta2</Version>
<Version>2.1.0</Version>
<FileVersion>2.0.0.0</FileVersion>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<RootNamespace>JWT</RootNamespace>
Expand All @@ -30,7 +30,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.22" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.25" />
</ItemGroup>

<Choose>
Expand Down
44 changes: 22 additions & 22 deletions src/JWT/Algorithms/NoneAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
using System;
using System.Security.Cryptography;

namespace JWT.Algorithms
{
/// <summary>
/// Implements the "None" algorithm.
/// </summary>
/// <see href="https://datatracker.ietf.org/doc/html/rfc7519#section-6">RFC-7519</see>
public sealed class NoneAlgorithm : IJwtAlgorithm
{
/// <inheritdoc />
public string Name => "none";

/// <inheritdoc />
using System;
using System.Security.Cryptography;

namespace JWT.Algorithms
{
/// <summary>
/// Implements the "None" algorithm.
/// </summary>
/// <see href="https://datatracker.ietf.org/doc/html/rfc7519#section-6">RFC-7519</see>
public sealed class NoneAlgorithm : IJwtAlgorithm
{
/// <inheritdoc />
public string Name => "none";

/// <inheritdoc />
public HashAlgorithmName HashAlgorithmName =>
throw new NotSupportedException("The None algorithm doesn't have any hash algorithm.");

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign) =>
throw new NotSupportedException("The None algorithm doesn't support signing.");
}
}
throw new NotSupportedException("The None algorithm doesn't have any hash algorithm.");

/// <inheritdoc />
public byte[] Sign(byte[] key, byte[] bytesToSign) =>
throw new NotSupportedException("The None algorithm doesn't support signing.");
}
}
Loading

0 comments on commit 1fad5d1

Please sign in to comment.