TBD54566975 / web5-go

Apache License 2.0
10 stars 6 forks source link

Add jwt.DecodeJWTClaims function #24

Closed KendallWeihe closed 6 months ago

KendallWeihe commented 7 months ago

Overview

Given the developer has a JWT, and they extract the base64 URL encoded claims, they can call this function to decode into the Claims type

Usage

package main

import (
    "encoding/json"
    "fmt"

    "github.com/tbd54566975/web5-go/jwt"
)

func main() {
        signedJwt := "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDpqd2s6ZXlKcmRIa2lPaUpQUzFBaUxDSmpjbllpT2lKRlpESTFOVEU1SWl3aWVDSTZJbFpyU3pKclRscDVVREY0WVZwUFoyZHBjREY2VTJwc1JXTnNTakZSTVhSQmMxRkJhVU5YUW1GaVgyOGlmUSMwIn0." +
    "eyJjX25vbmNlIjoiYWJjZDEyMyIsImlzcyI6ImV5SnJkSGtpT2lKUFMxQWlMQ0pqY25ZaU9pSkZaREkxTlRFNUlpd2llQ0k2SWxaclN6SnJUbHA1VURGNFlWcFBaMmRwY0RGNlUycHNSV05zU2pGUk1YUkJjMUZCYVVOWFFtRmlYMjhpZlEifQ." +
    "O434_IlnJOYbcIsHkVcOyKLxVvcfHvcg6tZL_gIU2TAY6qFiaPZ_w6upSzIl-I8JXYBytzR_8BfCq4P3che1Bg"

    parts := strings.Split(signedJwt, ".")

    base64UrlEncodedClaims := parts[1]

    decodedClaims, err := jwt.DecodeJWTClaims(base64UrlEncodedClaims)

    if err != nil {
        panic(err)
    }

    bytes, err := json.MarshalIndent(decodedClaims, "", "  ")
    if err != nil {
            panic(err)
    }

    fmt.Printf("Claims: %+v\n", string(bytes))
}

Additional Ideas

I followed the same pattern as we have with jws.DecodeJWSHeader() from here. But, we may want to go a add another layer here. Namely, the developer consuming this library (let's call them Alice), will likely have a full signed JWT string (which may originate from our jwt.Sign() function). And Alice may want both the headers & the claims (often times specs such as OID4VCI have verification rulesets which include both the header & the claims).

Rather than having the developer first do this...

parts := strings.Split(signedJwt, ".")
// todo error handle
base64UrlEncodedHeader := parts[0]
base64UrlEncodedClaims := parts[1]

...we may want to embed that in a function, as well as also executing a call to jwt.Verify(). I originally had the idea that we could create a function like this...

type JWT struct {
    Header jws.Header
    Claims jwt.Claims
}

func ParseJWT(signedJwt string) (JWT, error) {
    verified, err := jwt.Verify(signedJwt)
    if err != nil {
        // TODO handle error
    }
    if !verified {
        // TODO handle error
    }

    parts := strings.Split(signedJwt, ".")
    if len(parts) != 3 {
        // TODO handle error
    }

    base64UrlEncodedHeader := parts[0]
    base64UrlEncodedClaims := parts[1]
    // TODO check if base64UrlEncodedHeader & base64UrlEncodedClaims are proper base64 URL encoded strings?

    header, err := jws.DecodeJWSHeader(base64UrlEncodedHeader)
    if err != nil {
        // TODO handle error
    }

    claims, err := jwt.DecodeJWTClaims(base64UrlEncodedClaims)
    if err != nil {
        // TODO handle error
    }

    return JWT{Header: header, Claims: claims}, nil
}

@mistermoe what do you think of that idea ☝️ We can open a ticket & copy/paste this in if we like it.

KendallWeihe commented 7 months ago

Created an issue for the additional idea https://github.com/TBD54566975/web5-go/issues/25

mistermoe commented 7 months ago

@KendallWeihe thoughts on renaming both DecodeJWSHeader and DecodeJWTClaims to jws.DecodeHeader and jwt.DecodeClaims respectively? the added JWS and JWT in function names feels redundant given the namespace provided by the packages

KendallWeihe commented 6 months ago

@KendallWeihe thoughts on renaming both DecodeJWSHeader and DecodeJWTClaims to jws.DecodeHeader and jwt.DecodeClaims respectively? the added JWS and JWT in function names feels redundant given the namespace provided by the packages

Yeah nice, agreed! Done ✅