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

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

Open
james-d-elliott opened this issue Mar 18, 2024 · 0 comments
Open

Comments

@james-d-elliott
Copy link

james-d-elliott commented Mar 18, 2024

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
}
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

No branches or pull requests

1 participant