go and parsing token with jwt-go
Asked Answered
B

2

10

Could anyone tell me why the following (from https://github.com/dgrijalva/jwt-go) example doesn't work?

token, err := jwt.Parse(myToken, func(token *jwt.Token) ([]byte, error) {
    return myLookupKey(token.Header["kid"])
})

if err == nil && token.Valid {
    deliverGoodness("!")
} else {
    deliverUtterRejection(":(")
}

I get an error saying cannot use func literal (type func(*jwt.Token) ([]byte, error)) as type jwt.Keyfunc in argument to jwt.Parse

I've tried to use the code from couple of different jwt-go examples but always ended up with this same error.

Berghoff answered 15/9, 2014 at 12:31 Comment(0)
C
9

The function Parse expects

type Keyfunc func(*Token) (interface{}, error)

You need to return interface{}, not byte[] in your function literal.
(maybe using a byte.Buffer to wrap the byte[], that you can then read as in "Convert arbitrary Golang interface to byte array")

Gert Cuykens points out in the comments to issue 36: commit e1571c8 should have updated the example.
Other examples like this gist also need to be updated.

Coeducation answered 15/9, 2014 at 12:38 Comment(3)
Well, I'll be... That's it, now everything seems to work as expected. The funniest thing is that in the Github of jwt-go they use []byte in the example. Thanks!Berghoff
Note that the change to interface{} happened fairly recently: github.com/dgrijalva/jwt-go/commit/… Might want to ping the author to fix their documentation.Parathyroid
@GertCuykens good bug report. I have included it in the answer for more visibility.Coeducation
B
7

As mentioned in another answer, the most recent version of is github.com/dgrijalva/jwt-go is v3.2.0+incompatible. The docs are now outdated as the package's jwt.Keyfunc function now has this signature:

type Keyfunc func (*Token) (interface{}, error)

When parsing JWTs, this jwt package also authenticates them. Two things are required to authenticate JWTs.

  1. The JWT itself.
  2. The cryptographic key that was used to sign the JWT. Typically, this is a public key.

This is where the jwt.Keyfunc fits in. The return value of interface{} is later type asserted to a cryptographic key. For many use cases, this will be an RSA or ECDSA based algorithm. This means the return type is typically *ecdsa.PublicKey or *rsa.PublicKey. (HMAC keys can also be used, but I won't cover that use case.)

If your public keys are in PEM format, you might be interested in using the functions built into the jwt package: ParseECPublicKeyFromPEM and ParseRSAPublicKeyFromPEM .

Creating a public key data structure

If your public keys are in another format, you may need to build the *ecdsa.PublicKey and *rsa.PublicKey Go structs by populating their exported fields.

ECDSA

Here's the data structure to fill:

// PublicKey represents an ECDSA public key.
type PublicKey struct {
    elliptic.Curve
    X, Y *big.Int
}

The embedded elliptic.Curve is created by using the crypto/elliptic function associated with its length:

// Create the ECDSA public key.
publicKey = &ecdsa.PublicKey{}

// Set the curve type.
var curve elliptic.Curve
switch myCurve {
    case p256:
        curve = elliptic.P256()
    case p384:
        curve = elliptic.P384()
    case p521:
        curve = elliptic.P521()
}
publicKey.Curve = curve

For both X and Y, these are *big.Ints. If you have the bytes for these integers, creating a new *big.Int and using the SetBytes method.

publicKey.X = big.NewInt(0).SetBytes(xCoordinate)
publicKey.Y = big.NewInt(0).SetBytes(yCoordinate)

RSA

Here's the data structure to fill:

// A PublicKey represents the public part of an RSA key.
type PublicKey struct {
    N *big.Int // modulus
    E int      // public exponent
}

The modulus, N is a *big.Int. If you have the bytes for this integer, create a new *big.Int and using the SetBytes method.

publicKey.N = big.NewInt(0).SetBytes(modulus)

The exponent is a regular integer. So that should be strait forward, but if you have the bytes for this integer instead you can create an integer like this:

publicKey.E = int(big.NewInt(0).SetBytes(exponent).Uint64())

Create a jwt.Keyfunc

Now that the public keys are in the right data strucuture, it's time to create a jwt.Keyfunc. As mentioned before, the input for this function will be a *jwt.Token and the output will be either an *ecdsa.PublicKey or a *rsa.PublicKey, but typed as an empty interface: interface{}, or an error.

The *jwt.Token contains the JWT itself, but the best way to identify which public key it's associated with is the kid. The kid is a string value contained in the JWT header. Read about the kid parameter in the RFC. It can be read from the JWT like this:

// ErrKID indicates that the JWT had an invalid kid.
ErrKID := errors.New("the JWT has an invalid kid")

// Get the kid from the token header.
kidInter, ok := token.Header["kid"]
if !ok {
    return nil, fmt.Errorf("%w: could not find kid in JWT header", ErrKID)
}
kid, ok := kidInter.(string)
if !ok {
    return nil, fmt.Errorf("%w: could not convert kid in JWT header to string", ErrKID)
}

At this point, the input is a kid and the output is the public key. The simplest implementation of a jwt.Keyfunc at this point is a function that reads from a map[string]interface{} where the key string is a kid and value interface{} is its public key.

Here's an example:

// ErrKID indicates that the JWT had an invalid kid.
ErrKID := errors.New("the JWT has an invalid kid") // TODO This should be exported in the global scope.

// Create the map of KID to public keys.
keyMap := map[string]interface{}{"zXew0UJ1h6Q4CCcd_9wxMzvcp5cEBifH0KWrCz2Kyxc": publicKey}

// Create a mutex for the map of KID to public keys.
var mux sync.Mutex

// Create the jwt.Keyfunc
var keyFunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {

    // Get the kid from the token header.
    kidInter, ok := token.Header["kid"]
    if !ok {
        return nil, fmt.Errorf("%w: could not find kid in JWT header", ErrKID)
    }
    kid, ok := kidInter.(string)
    if !ok {
        return nil, fmt.Errorf("%w: could not convert kid in JWT header to string", ErrKID)
    }

    // Get the appropriate public key from the map of KID to public keys.
    mux.Lock()
    publicKey, ok := keyMap[kid]
    mux.Unlock()
    if !ok {
        return nil, fmt.Errorf("%w: could not find a matching KID in the map of keys", ErrKID)
    }

    return publicKey, nil
}

You can now use the created keyFunc function-as-a-variable when parsing JWTs.

// Parse the JWT.
token, err := jwt.Parse(myToken, keyFunc)
if err != nil {
    log.Fatalf("Failed to parse JWT.\nError: %s\n", err.Error())
}

// Confirm the JWT is valid.
if !token.Valid {
    log.Fatalln("JWT failed validation.")
}

// TODO Proceed with authentic JWT.

Other JWT public key formats

JWTs' public keys can also be described by RFC 7517. This RFC describes a JSON Web Key (JWK) and JSON Web Key Set (JWKs). Some identity providers, like Keycloak or Amazon Cognito (AWS) provide these via HTTPS endpoints.

Here's an example JWKs:

{
  "keys": [
    {
      "kid": "zXew0UJ1h6Q4CCcd_9wxMzvcp5cEBifH0KWrCz2Kyxc",
      "kty": "RSA",
      "alg": "PS256",
      "use": "sig",
      "n": "wqS81x6fItPUdh1OWCT8p3AuLYgFlpmg61WXp6sp1pVijoyF29GOSaD9xE-vLtegX-5h0BnP7va0bwsOAPdh6SdeVslEifNGHCtID0xNFqHNWcXSt4eLfQKAPFUq0TsEO-8P1QHRq6yeG8JAFaxakkaagLFuV8Vd_21PGJFWhvJodJLhX_-Ym9L8XUpIPps_mQriMUOWDe-5DWjHnDtfV7mgaOxbBvVo3wj8V2Lmo5Li4HabT4MEzeJ6e9IdFo2kj_44Yy9osX-PMPtu8BQz_onPgf0wjrVWt349Rj6OkS8RxlNGYeuIxYZr0TOhP5F-yEPhSXDsKdVTwPf7zAAaKQ",
      "e": "AQAB",
      "x5c": [
        "MIICmzCCAYMCBgF4HR7HNDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjEwMzEwMTcwOTE5WhcNMzEwMzEwMTcxMDU5WjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCpLzXHp8i09R2HU5YJPyncC4tiAWWmaDrVZenqynWlWKOjIXb0Y5JoP3ET68u16Bf7mHQGc/u9rRvCw4A92HpJ15WyUSJ80YcK0gPTE0Woc1ZxdK3h4t9AoA8VSrROwQ77w/VAdGrrJ4bwkAVrFqSRpqAsW5XxV3/bU8YkVaG8mh0kuFf/5ib0vxdSkg+mz+ZCuIxQ5YN77kNaMecO19XuaBo7FsG9WjfCPxXYuajkuLgdptPgwTN4np70h0WjaSP/jhjL2ixf48w+27wFDP+ic+B/TCOtVa3fj1GPo6RLxHGU0Zh64jFhmvRM6E/kX7IQ+FJcOwp1VPA9/vMABopAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALILq1Z4oQNJZEUt24VZcvknsWtQtvPxl3JNcBQgDR5/IMgl5VndRZ9OT56KUqrR5xRsWiCvh5Lgv4fUEzAAo9ToiPLub1SKP063zWrvfgi3YZ19bty0iXFm7l2cpQ3ejFV7WpcdLJE0lapFdPLo6QaRdgNu/1p4vbYg7zSK1fQ0OY5b3ajhAx/bhWlrN685owRbO5/r4rUOa6oo9l4Qn7jUxKUx4rcoe7zUM7qrpOPqKvn0DBp3n1/+9pOZXCjIfZGvYwP5NhzBDCkRzaXcJHlOqWzMBzyovVrzVmUilBcj+EsTYJs0gVXKzduX5zO6YWhFs23lu7AijdkxTY65YM0="
      ],
      "x5t": "IYIeevIT57t8ppUejM42Bqx6f3I",
      "x5t#S256": "TuOrBy2NcTlFSWuZ8Kh8W8AjQagb4fnfP1SlKMO8-So"
    }
  ]
}

In the above example, a *rsa.PublicKey can be created from just the n and e JSON attributes. It's kid attribute can be used to create an entry in the keyMap described in an earlier section.

I've actually run into this use case before and created a Go package just for consuming JWKs and creating jwt.Keyfunc. If this fits your use case, check out the repository here: github.com/MicahParks/keyfunc.

Balliett answered 27/5, 2021 at 15:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.