golang-jwt / jwt

Go implementation of JSON Web Tokens (JWT).
https://golang-jwt.github.io/jwt/
MIT License
6.98k stars 335 forks source link

Question / FR: Subsequent Verification of an Unverified Token #384

Open james-d-elliott opened 5 months ago

james-d-elliott commented 5 months ago

In certain scenarios it's nice to perform the validation of a token separate parsing it. For example when checking client assertions in OAuth 2.0 flows so that the authorization server and client policies can be checked prior to checking the various elements.

I know this probably can be technically be done already (including examples, which I'd love feedback on). But I'm wondering if there is existing tooling to make this simpler or if there would be interest in adding it?

Thinking of adding a secondary function to the parser which optionally forcefully performs the verification (bool parameter to replace p.skipClaimsValidation). This function would be called from ParseWithClaims to replace the existing logic which would almost be identical.

Below is an abstract example of what I think will work without the additional method (but would really love someone to correct me if there is something I've missed):

func main() {
    var (
        block *pem.Block
        key   *rsa.PublicKey
        err   error
    )

    rawRSAPRivateKey := `-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBALxmjwHpA1Huhp/OJX7x/lx1nceKK0a6LhO57q+h7HxtTq8aSasTcNdF
Gn3lru+2NXlrxxXgoMgXcsCJFDjQX3qE3H1K9qLEyZ0LA7kMtPZ6kGi95cLFkG6P
Ou4hBLXBud09zrvS8rhGOESIR72je2CkULnsK9rRw6CPDeqSrGlxAgMBAAE=
-----END RSA PUBLIC KEY-----
`
    abstractTokenRaw := "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtpZC1mb28iLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tIiwiZXhwIjoxNzQyMjY5NjU2LCJpc3MiOiJiYXIiLCJqdGkiOiIxMjM0NSIsInN1YiI6ImJhciJ9.K8WX56WRETtecxjaZCl3eTnTnv3fXM9L-64pHGUOuMVk-6lco2yfhfnNc2VaH3DJzEnTDOZGAWPKq9gccycxFr8x_oeOJ9oGP_F18-E-q2KrNf-tqAI3zV9KSEkrH3-j94YtBys9PxcTFulYUtJzs2_1c2PldbRVmgkbhjYTOBA"

    block, _ = pem.Decode([]byte(rawRSAPRivateKey))

    if key, err = x509.ParsePKCS1PublicKey(block.Bytes); err != nil {
        panic(err)
    }

    opts := []jwt.ParserOption{
        jwt.WithStrictDecoding(),
        jwt.WithAudience("https://example.com"),
        jwt.WithExpirationRequired(),
        jwt.WithIssuedAt(),
        jwt.WithoutClaimsValidation(),
    }

    parser := jwt.NewParser(opts...)

    token, parts, err := parser.ParseUnverified(abstractTokenRaw, &jwt.MapClaims{})
    if err != nil {
        panic(err)
    }

    // Abstract Business / Domain Logic

    if err = VerifyParsed(token, parts, key, opts...); err != nil {
        panic(err)
    }

    fmt.Printf("Token Valid: %t\n", token.Valid)
}

func VerifyParsed(token *jwt.Token, parts []string, key any, opts ...jwt.ParserOption) (err error) {
    if token.Signature, err = jwt.NewParser(opts...).DecodeSegment(parts[2]); err != nil {
        return err
    }

    text := strings.Join(parts[0:2], ".")

    switch have := key.(type) {
    case jwt.VerificationKeySet:
        if len(have.Keys) == 0 {
            return jwt.ErrTokenUnverifiable
        }

        for _, k := range have.Keys {
            if err = token.Method.Verify(text, token.Signature, k); err == nil {
                break
            }
        }
    default:
        err = token.Method.Verify(text, token.Signature, have)
    }

    if err != nil {
        return err
    }

    if err = jwt.NewValidator(opts...).Validate(token.Claims); err != nil {
        return err
    }

    token.Valid = true

    return nil
}