Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add jwt.DecodeJWTClaims function #24

Merged
merged 3 commits into from
Feb 7, 2024
Merged

Conversation

KendallWeihe
Copy link
Contributor

@KendallWeihe KendallWeihe commented Feb 5, 2024

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
Copy link
Contributor Author

Created an issue for the additional idea #25

nonce := "abcd123"
claims := jwt.Claims{
Issuer: did.ID,
Misc: map[string]interface{}{"c_nonce": nonce},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface{} still works, but there's an alias for it now which is much more convenient: any

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO I prefer the language-native way of interface ... less convenient but more clear (at least to newbies)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh nice @alecthomas i had no idea about the alias

jwt/jwt_test.go Outdated Show resolved Hide resolved
@mistermoe
Copy link
Member

@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
Copy link
Contributor Author

@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 ✅

@KendallWeihe KendallWeihe merged commit 4ebf03f into main Feb 7, 2024
@KendallWeihe KendallWeihe deleted the kendallw/decode-claims branch February 20, 2024 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants