Add Sign in with Apple to the back-end

At WWDC 2019, Apple introduced a new user authorization system - Sign in with Apple. There was a task to integrate it into our back-end and synchronize it with existing authorization methods using email, Google and Facebook. Our colleague kurenkoff took up the task, he is the author of this article. Those interested are requested under cat.



User registration and authorization procedure through Apple





The procedure is quite primitive and occurs exactly as indicated on the diagram from Apple.



In addition, Apple provides the ability to update the token:







The scheme is also quite simple, it is possible to do token verification, but we do not use this feature, because we do not need it.



Implement authorization through AppleID



To implement authorization through AppleID, we use the appleLogin package. The author of this package made some mistakes, but they are not critical (and some were jointly fixed). First of all, you need to initialize the config using the data received through the Apple developer portal.



config := appleLogin.InitAppleConfig( TeamID, // ID   developer.apple.com,   iOS  ClientID, // Bundle  iOS  callbackURI, //       KeyID, //     developer.apple.com,   iOS  ) privateKey := os.Getenv("PRIVATE_KEY") err := config.LoadP8CertByByte([]byte(privateKey)) if err != nil { return nil, err }
      
      





Then get the token:



 token, err := config.GetAppleToken(clientToken, tokenExpireTime) if err != nil { return nil, err }
      
      





It is important to note which request is sent to the Apple server. The documentation says that for authorization it is necessary to send the client_id, client_secret, code, grant_type, redirect_uri fields. All of these fields are described as required, but redirect_uri can be omitted. The main difficulty is client_secret - this is a JWT signed by a key generated on the WWDR portal:



 token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{ "iss": a.TeamID, "iat": time.Now().Unix(), "exp": time.Now().Unix() + expireTime, "aud": "https://appleid.apple.com", "sub": a.ClientID, }) token.Header = map[string]interface{}{ "kid": a.KeyID, "alg": "ES256", } tokenString, _ := token.SignedString(a.AESCert)
      
      





The Apple API will either respond with an error or return a structure. We are interested in the IDToken field:



 type AppleAuthToken struct { AccessToken string json:"access_token" //AccessToken ExpiresIn int64 json:"expires_in" //Expires in IDToken string json:"id_token" //ID token RefreshToken string json:"refresh_token" //RF token TokenType string json:"token_type" //Token Type }
      
      





IDToken is a JWT token containing user data:



 type AppleUser struct { ID string json:"sub,omitempty" Email string json:"email,omitempty" EmailVerified bool json:"email_verified,string,omitempty" }
      
      





It is worth paying attention to the fact that email can be received only at the first authorization. If you try to reauthorize, you can only get an ID (a unique user identifier in Sign in with Apple). To register a user, we need enough of these data.



All Articles