How to sign JWT with ECDSA method using jwt golang package - Sign in with Apple

Viewed 1519

When integrating Sign in with Apple you generate a key in your apple developer account.
It's a file that is named like AuthKey_3JMD5K6.p8 and looks like

-----BEGIN PRIVATE KEY-----
MasdfjalskdasdflaASDFAadsflkjaADSFAewfljasdfljkasefasdflkjasdf
asdfljkasdfASDFASDFoiqretasdoiyjlfsbgREtaREGSDFBREtafsrgAREGfdsgaregR
LKJIOEWFNLasdflkawefjoiasdflk
-----END PRIVATE KEY-----

so I made a var appleKey := MasdfjalskdasdflaASDFAadsflkjaADSFAewfljasdfljkasefasdflkjasdf asdfljkasdfASDFASDFoiqretasdoiyjlfsbgREtaREGSDFBREtafsrgAREGfdsgaregRLKJIOEWFNLasdflkawefjoiasdflk

I've signed jwt with the HMAC-SHA method before which is fairly straightforward but I don't know how to sign a jwt with the ECDSA method.

I wrote my code the same way I did for the HMAC-SHA method but get an error key is of invalid type

So using the the jwt library for golang how can I sign my jwt with ECDSA method?

My code

  // generate client secret jwt using apple key
  expirationTime := time.Now().Add(5 * time.Minute)
  claims := &Claims{
    StandardClaims: jwt.StandardClaims {
      Audience: "https://appleid.apple.com",
      Subject: "com.app.ios",
      Issuer: string(appleTeamId),
      ExpiresAt: expirationTime.Unix(),
      IssuedAt: time.Now().Unix(),
    },
  }
  appleToken := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
  appleToken.Header["kid"] = appleKid

  signedAppleToken, err := appleToken.SignedString(appleKey)

I now know this isn't how you do it and it's a little bit more complex than that but what is the way to do it?

I found this article that tells you how to manually do it:
http://p.agnihotry.com/post/validating_sign_in_with_apple_authorization_code/

But I'm already using the jwt library for golang for the other part of the token:
https://godoc.org/github.com/dgrijalva/jwt-go

1 Answers

In the github.com/dgrijalva/jwt-go's SigningMethodECDSA.Sign docs you can find:

[...] For this signing method, key must be an ecdsa.PrivateKey struct

So, to put together an example:

    p8bytes, err := ioutil.ReadFile("SomeAppleKey.p8")
    if err != nil {
      log.Println(err)
      return
    }

    // Here you need to decode the Apple private key, which is in pem format
    block, _ := pem.Decode(p8bytes)
    // Check if it's a private key
    if block == nil || block.Type != "PRIVATE KEY" {
      log.Println("Failed to decode PEM block containing private key")
      return
    }
    // Get the encoded bytes
    x509Encoded := block.Bytes

    token := jwt.NewWithClaims(
        jwt.SigningMethodES256, // specific instance of `*SigningMethodECDSA`
        jwt.StandardClaims{
            // ...
        },
    )

    // Now you need an instance of *ecdsa.PrivateKey
    parsedKey, err := x509.ParsePKCS8PrivateKey(x509Encoded) // EDIT to x509Encoded from p8bytes
    if err != nil {
        panic(err)
    }

    ecdsaPrivateKey, ok := parsedKey.(*ecdsa.PrivateKey)
    if !ok {
        panic("not ecdsa private key")
    }

    // Finally sign the token with the value of type *ecdsa.PrivateKey
    signed, err := token.SignedString(ecdsaPrivateKey)
    if err != nil {
        panic(err)
    }
    fmt.Println(signed) // the signed JWT

Note: as shown in the code snippet, because the key file from Apple is in PEM format, it needs to be decoded first


Warning!

Please be aware that github.com/dgrijalva/jwt-go has been unmaintained for a long time and has critical unfixed bugs. And doesn't support Go modules, before the version 4 (which is just a preview anyway). I strongly recommend to choose an different library for dealing with JWT.

Update June 2021

There is now an official community fork of the library: golang-jwt/jwt blessed by the owner of the original project.

Related