dgrijalva / jwt-go

ARCHIVE - Golang implementation of JSON Web Tokens (JWT). This project is now maintained at:
https://github.com/golang-jwt/jwt
MIT License
10.78k stars 996 forks source link

Add an example for parsing RS256 tokens #438

Open mieubrisse opened 4 years ago

mieubrisse commented 4 years ago

The major point of confusion comes from what the key func should be returning, since the examples only have HS256 tokens that return a string (but which errors for the RSA alg). I had to do a decent amount of code-diving to discover that the expected type is actually rsa.PublicKey.

vidolch commented 4 years ago

@mieubrisse can you add the working code here since I am wondering how to get it working as well...

mieubrisse commented 4 years ago

Sure:

const (
    // Key in the Headers hashmap of the token that points to the key ID
    keyIdTokenHeaderKey = "kid"

    // Header and footer to attach to base64-encoded key data that we receive from Auth0
    pubKeyHeader = "-----BEGIN CERTIFICATE-----"
    pubKeyFooter = "-----END CERTIFICATE-----"
)

// Provided by Auth0 via https://auth0.com/docs/tokens/json-web-tokens/json-web-key-sets
var RsaPublicKeyBase64 = map[string]string{
    "some-key-id1": "MIIDD......",
    "some-key-id2": "MIIDD......",
}

token, err := new(jwt.Parser).ParseWithClaims(
    tokenStr,
    &auth0_authorizer.Auth0TokenClaims{}, // This is a custom claims object
    func(token *jwt.Token) (interface{}, error) {
            // IMPORTANT: Validating the algorithm per https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac
            if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
                    return nil, stacktrace.NewError(
                            "Expected token algorithm '%v' but got '%v'",
                            jwt.SigningMethodRS256.Name,
                            token.Header)
            }

            untypedKeyId, found := token.Header[keyIdTokenHeaderKey]
            if !found {
                    return nil, stacktrace.NewError("No key ID key '%v' found in token header", keyIdTokenHeaderKey)
            }
            keyId, ok := untypedKeyId.(string)
            if !ok {
                    return nil, stacktrace.NewError("Found key ID, but value was not a string")
            }

            keyBase64, found := auth0_constants.RsaPublicKeyBase64[keyId]
            if !found {
                    return nil, stacktrace.NewError("No public RSA key found corresponding to key ID from token '%v'", keyId)
            }
            keyStr := pubKeyHeader + "\n" + keyBase64 + "\n" + pubKeyFooter

            // Since the token is RSA (which we validated at the start of this function), the return type of this function actually has to be rsa.PublicKey!
            pubKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(keyStr))
            if err != nil {
                    return nil, stacktrace.Propagate(err, "An error occurred parsing the public key base64 for key ID '%v'; this is a code bug", keyId)
            }

            return pubKey, nil
    },
)

The non-obvious parts:

  1. The "kid" is a reference ID, which will be used to tell you which public key you should return from the function. In my case, Auth0 provides a set of public keys and the "kid" will point to one of those
  2. The token extractor function needs to validate that the signing algorithm is what we expect
  3. The token extractor function for RSA tokens needs to return type rsa.PublicKey (I only found this by diving through jwt-go code)
lggomez commented 3 years ago

Hi @mieubrisse per #462 this repo has been deprecated and migrated to a new organization: https://github.com/golang-jwt/jwt

As stated in golang-jwt/jwt#7, none if the new maintainers will be copying the original contributions of interest to respect the authors, but will rather encourage them to migrate to the new repo (and also check if their contribution is still applicable)

Regards

lggomez commented 3 years ago

Related issue: https://github.com/golang-jwt/jwt/issues/58