Sign In with Apple using JavaScript

Implementing Sign in with Apple using Apple JS SDK in frontend and Apple REST API + NodeJS in backend

Sign In with Apple using JavaScript
Photo by Andy Wang / Unsplash

Apple recently announced “Sign In with Apple”, its privacy focused authentication solution that allows developers to authenticate users across several platforms.

Sign In with Apple makes it easy for users to sign in to your apps and websites using their Apple ID. Instead of filling out forms, verifying email addresses, and choosing new passwords, they can use Sign In with Apple to set up an account and start using your app right away. All accounts are protected with two-factor authentication for superior security, and Apple will not track users’ activity in your app or website.

Let’s see a quick demo:

In the demo application I’ve used Sign In with Apple JS (https://developer.apple.com/documentation/signinwithapplejs/configuring_your_webpage_for_sign_in_with_apple) and Sign In with Apple REST APIs to implement the complete authentication flow.

Step 1: Get your OAuth credentials

Apple has made this process hard and time consuming, unlike other authentication providers generating the client id and client secret is not straight forward. You can follow the steps provided here to get your credentials: https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple

Note: I’ve used jsonwebtoken library in NodeJS to generate the client secret which is different from the implementation shown in ruby as per Okta blog.

const getClientSecret = () => {
 // sign with RSA SHA256
 const privateKey = fs.readFileSync(process.env.PRIVATE_KEY_FILE_PATH);
 const headers = {
  kid: process.env.KEY_ID,
  typ: undefined // is there another way to remove type?
 }
 const claims = {
  'iss': process.env.TEAM_ID,
  'aud': 'https://appleid.apple.com',
  'sub': process.env.CLIENT_ID,
 }
token = jwt.sign(claims, privateKey, {
  algorithm: 'ES256',
  header: headers,
  expiresIn: '24h'
 });
return token
}
Snippet for generating client secret

Step 2: Start OAuth flow

Once you have the credentials in hand, we can use Apple’s JS SDK to setup our frontend to display the sign in button and start the authentication flow.

<div class="login-btn" id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
 <script type="text/javascript"
  src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
<script type="text/javascript">
  AppleID.auth.init({
   clientId: 'com.techulus.client',
   scope: 'name email',
   redirectURI: 'http://techulus.com:4000/callback'
  });
const buttonElement = document.getElementById('appleid-signin');
  buttonElement.addEventListener('click', () => {
   AppleID.auth.signIn();
  });
</script>
Snippet for displaying sign in button

In the above code snippet we’ve initialised the SDK and assigned the on click event for a div (sign in button) to start the authentication flow. When the users click the sign in button they will be redirected to Apple’s login page and they can login using their Apple ID. Once the users have successfully completed login, Apple will redirect the users back to our application to the specified redirect URI along with an authorization code.

Step 3: Validate the user

We can now validate the authorization code with Apple to obtain the access token or validate an existing refresh token using REST API endpoints.(https://developer.apple.com/documentation/signinwithapplerestapi/generate_and_validate_tokens).

app.post('/callback', bodyParser.urlencoded({ extended: false }), (req, res) => {
 const clientSecret = getClientSecret()
 const requestBody = {
  grant_type: 'authorization_code',
  code: req.body.code,
  redirect_uri: process.env.REDIRECT_URI,
  client_id: process.env.CLIENT_ID,
  client_secret: clientSecret,
  scope: process.env.SCOPE
 }
axios.request({
  method: "POST",
  url: "https://appleid.apple.com/auth/token",
  data: querystring.stringify(requestBody),
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
 }).then(response => {
  return res.json({
   success: true,
   data: response.data
  })
 }).catch(error => {
  return res.status(500).json({
   success: false,
   error: error.response.data
  })
 })
})
Snippet for handling user validation

Once the request has been completed successfully you will get the access token and an id token which can be parsed to get a unique identifier for the user. Note that we do not have to verify the signature of the token, we can just directly parse the token using base64 decode and then use the sub value as the users unique id.

Parsed id token:

Checkout the entire code on GitHub (https://github.com/arjunkomath/sign-in-with-apple-js-node-example)

arjunkomath/sign-in-with-apple-js-node-example
Sign in with Apple using Apple JS and REST API. Contribute to arjunkomath/sign-in-with-apple-js-node-example…