Search code examples
gojwtecdsa

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


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


Solution

  • 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.