gojwtnext-authjwe

How to validate JWE token in Golang


I've got 2 questions but first I want to provide some context:

On our webapp, we are using NextAuth to generate jwt tokens which then we attach to requests against our Golang server (for fetching resources).

The generated tokens seem to be JWE tokens generated via A256GCM. In our golang server we want to validate the token and extract a few custom claims of it. That said, we're struggling to find a way to do the decryption. We're using go-jose as follows:

rawToken := `eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..aiIqD7-cU8Hu92F8.Kx2k99cyLYJR1P0xK_1wUsVO521T7kYSKx-OEutVJcpzbX27hZH0kh2MlBLxQHdmc8q4uXglhjl4JE3nTp_c6nOjga-faHyxYqKrZGJFLlu9MC4JVUWyonX6doFq0gl3UX9ABtP2t35Qly-w1qKH8BdG9x4iB1YM-yvs1w-HpBbMFQR7U7X4oHWIh_YJQlWADesYq6da7A97GSSXs2Go6yb7SH5WWd7iQzDu-UO6eg._PqujCUyMUqOkID80vJiDw`
key := []byte("thisisaverylongtextusedforhashing")

enc, err := jwt.ParseEncrypted(rawToken)
if err != nil {
    panic(err)
}

out := jwt.Claims{}
if err := enc.Claims(key, &out); err != nil {
    panic(err)
}
fmt.Printf("iss: %s, sub: %s\n", out.Issuer, out.Subject)

We are getting: panic: square/go-jose: error in cryptographic primitive

PS: secret I pass to nextAuth used for the JWE generation: thisisaverylongtextusedforhashing

Raw JWE token that NextAuth outputs and which I want to validate in my golang server: eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..aiIqD7-cU8Hu92F8.Kx2k99cyLYJR1P0xK_1wUsVO521T7kYSKx-OEutVJcpzbX27hZH0kh2MlBLxQHdmc8q4uXglhjl4JE3nTp_c6nOjga-faHyxYqKrZGJFLlu9MC4JVUWyonX6doFq0gl3UX9ABtP2t35Qly-w1qKH8BdG9x4iB1YM-yvs1w-HpBbMFQR7U7X4oHWIh_YJQlWADesYq6da7A97GSSXs2Go6yb7SH5WWd7iQzDu-UO6eg._PqujCUyMUqOkID80vJiDw.


Solution

  • Given your input, I put together a response that could help you with your issue. First, I used version 2 of the package gopkg.in/go-jose/go-jose.v2 because (from what I saw) the algorithm A256GCM is not fully compatible in the newest version of the package that should be version 3. Below you can find the relevant code:

    package main
    
    import (
        "crypto/rand"
        "crypto/rsa"
        "fmt"
        "io"
        "os"
        "time"
    
        "github.com/golang-jwt/jwt"
        jose_jwt "gopkg.in/go-jose/go-jose.v2"
    )
    
    type CustomClaims struct {
        Username string `json:"username"`
        Password string `json:"password"`
        jwt.StandardClaims
    }
    
    func main() {
        privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
        if err != nil {
            panic(err)
        }
    
        // generate token
        token, err := generateToken()
        if err != nil {
            panic(err)
        }
    
        publicKey := &privateKey.PublicKey
        encrypter, err := jose_jwt.NewEncrypter(jose_jwt.A256GCM, jose_jwt.Recipient{
            Algorithm: jose_jwt.RSA_OAEP_256,
            Key:       publicKey,
        }, nil)
        if err != nil {
            panic(err)
        }
    
        plainText := []byte(token)
        object, err := encrypter.Encrypt(plainText)
        if err != nil {
            panic(err)
        }
    
        serialized := object.FullSerialize()
    
        object, err = jose_jwt.ParseEncrypted(serialized)
        if err != nil {
            panic(err)
        }
    
        decrypted, err := object.Decrypt(privateKey)
        if err != nil {
            panic(err)
        }
    
        fmt.Println(string(decrypted))
    
        // parse token
        claims, err := ValidateToken(string(decrypted))
        if err != nil {
            panic(err)
        }
    
        fmt.Println(len(claims))
    }
    

    Here, we first generate a private key to encrypt the token and then decrypt it through its public key. I omitted the code used to generate and validate a JWT token for brevity. To test out the solution I added two custom claims to the generated token (username and password that are defined in the CustomClaims struct). Then, when we parse the token, we'll be able to retrieve their values.
    Let me know if this helps you!