Search code examples
c#.netpaseto

How to create consistent PasetoSymmetricKey with adaviddesmet/paseto-dotnet nuget package?


I have started two development environment up with dotnet watch run --urls=http://localhost:5001 command.

One instance is generating a PASETO token the other one is decoding it, the decoding instance fails with "Invalid Token".

I have used the following code with paseto-dotnet nuget package.

using System.Text.Json;
using FluentResults;
using Paseto;
using Paseto.Builder;
using Paseto.Cryptography.Key;

public static class TokenUtils
{
    readonly static PasetoSymmetricKey pasetoSymmetricKey = new PasetoBuilder()
        .UseV4(Purpose.Local)
        .WithSharedKey(Encoding.UTF8.GetBytes("DummySecret"))
        .GenerateSymmetricKey();

    public static string GenerateLocalToken(TokenPayload tokenPayload)
    {
        return new PasetoBuilder()
            .UseV4(Purpose.Local)
            .WithKey(pasetoSymmetricKey)
            .AddClaim("UserId", tokenPayload.AppId)
            .IssuedAt(DateTime.UtcNow)
            .Expiration(tokenPayload.ExpiresAt)
            .Encode();
    }

    public static Result<TokenPayload> DecodeLocalToken(string token)
    {
        try
        {
            PasetoTokenValidationResult decodedResult = new PasetoBuilder()
                .UseV4(Purpose.Local)
                .WithSharedKey(Encoding.UTF8.GetBytes("DummySecret"))
                .WithKey(pasetoSymmetricKey)
                .Decode(token);
            if (decodedResult is null) { return Result.Fail("Failed to Decode Token"); }
            if (!decodedResult.IsValid) { return Result.Fail("Invalid Token"); }
            TokenPayload? tokenPayload = JsonSerializer.Deserialize<TokenPayload>(decodedResult.Paseto.RawPayload);
            if (tokenPayload is null) { return Result.Fail("Failed to Deserialize TokenPayload"); }
            return Result.Ok(tokenPayload);
        }
        catch (Exception exception)
        {
            return Result.Fail(exception.Message);
        }
    }
}
using System.Text.Json.Serialization;

public class TokenPayload
{
    public string UserId { get; set; } = default!;
    [JsonPropertyName("iat")]
    public DateTime IssuedAt { get; set; } = default!;
    [JsonPropertyName("exp")]
    public DateTime ExpiresAt { get; set; } = default!;
}


Solution

  • The GenerateSymmetricKey() method is for generating a random symmetric key, regardless of the specified shared key. That's an issue from my side since I didn't clarify enough the use case of such method. I will update the code to reflect that.

    Anyway, for passing a key to the PasetoBuilder you should be using PASERK. Currently, there's no quick way to create that so I will be adding a new method to the PasetoBuilder.

    In the meantime, for creating your own PasetoKey using PASERK, you can use the following code (for V4 Local in your case):

    // I'm generating the key but you can supply your own, just make sure it has a length of 256 bits (32 bytes) which is required for V4 Local
    var k = new byte[32];
    RandomNumberGenerator.Fill(k);
    
    var pasetoSymmetricKey = new PasetoSymmetricKey(k, new Version4());
    
    var paserk = Paserk.Encode(pasetoSymmetricKey, PaserkType.Local);
    

    So your code for decoding becomes:

    var key = Paserk.Decode(paserk);
    
    var decodedResult = new PasetoBuilder()
                    .UseV4(Purpose.Local)
                    .WithKey(key)
                    .Decode(token);
    

    Update: Paseto.Core 1.1.1 has been published which includes the GenerateSerializedKey method for serializing a Paseto Key in PASERK format.