Sign in With Apple Next JS

Sign in With Apple Next JS

Sign In With Apple with Next JS and Next Auth

Table Of Contents

What's sign in with Apple?

@Airport will soon allow users accounts so we can start launching some of the features we've been planning. One of the things we needed was handling User activity and allowing them to log in. We thought of Sign in with apple as an easy way to allow access to our app.

Sign in with Apple is a way you can authenticate users into your application, with their Apple ID. It is one of the multitudes of different ways of using OAuth / OIDC to allow users into your apps, without managing the infrastructure behind the login system. It does make the login process a breeze since you no longer need to manage your auth servers and the complexity behind them.

The thing with Sign in with Apple, it is quite different in how you integrate it into a modern web application. I was looking for guides that covered it but didn't quite find any, so I'm writing my own in hopes that others find it and can configure sign in with apple within 10 mins, in their NextJS Applications.

Why Next JS

Airport currently still uses React as its core Library. We did find some repetitive patterns we were using, and it was turning into a pain in managing SEO and Server Side Rendering ourselves. We decided to switch to NextJS while it was still early in Airport's development cycle. We're continuing migrations, but we're excited about the opportunities Next JS creates for the Airport and the apps on our platform. We want apps on Airport to be discoverable to the masses, so having high SEO Rankings and Meta tags for Dynamic content sharing is a must. NextJS makes it super easy to build a performant website while still using React, a language we know, and love dearly.

With that said lets, integrate some Auth! If you want to skip ahead and check out the code for Sign in with Apple, you can click here →.

Configuring Apple Developer Accounts

The most unclear part I felt about integrating Sign in With Apple for Web was how to configure your Apple Developer account. Firstly, you must have Apple Developer, as in you must be paying 99$ a year for their Development Fees. Once you're apart of the Apple Developer Program, you can integrate Sign in With Apple.

  1. Head to Certificates, Identifiers, and Profiles on your Apple Dev account.
  2. Firstly we need to create an App ID.
  3. Make sure to select Sign in With Apple and Enable as Primary App ID.
  4. Click Continue and then Save.
  5. Next, We need to make a new Service ID.
  6. On the right of the 🔍 glass select the drop-down and click Service IDs.
  7. Click the Blue ➕ button.
  8. Select Service ID.
  9. Enter a description and an identifier. It can be your domain or any random string. Apple recommends a "reverse-domain name style string", but anything works. The identifier must be unique. For mine, i Entered next-auth.test.app
  10. Click Continue and then click on the identifier you just created. (Make sure you're looking at Service IDs, not App IDs)
  11. Click the Configure Button, next to the Sign In with Apple (make sure it's enabled ☑️).
  12. So now we need to add some URL's basically, we need to enter the Base Domain of our website where Sign in with Apple is called from, and the Callback URL, which is essentially where Apple Redirects us once we have the token. Now, there's a multitude of ways to do this but at this point, I decided to take the help of a library called NextAuth which simplifies this process, and honestly handles the whole auth process for you. I strongly urge you to use this library.

Setting up our Next JS App with Next Auth

  1. Create a new Next JS app with npx create-next-app auth-test
  2. Install the dependency yarn add next-auth
  3. Go into the pages folder and delete everything in index.js (I left the base styling in).
  4. Go into _app.js and pull in the next-auth/client

pages/_app.js

import '../styles/globals.css';
import { Provider } from 'next-auth/client';
function MyApp({ Component, pageProps }) {
  return (
    <Provider>
      <Component {...pageProps} />
    </Provider>
  );
}
 
export default MyApp;
  1. Now go back into index.js and paste the following code (you can overwrite your existing code)

pages/index.js

import Head from 'next/head';
import Link from 'next/link';
import styles from '../styles/Home.module.css';
import { signIn, signOut, useSession } from 'next-auth/client';
 
export default function Home() {
  const [session] = useSession();
  const handleLogin = (e) => {
    e.preventDefault();
    signIn();
  };
 
  const handleLogout = (e) => {
    e.preventDefault();
    signOut();
  };
 
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1>Home</h1>
      <>
        {session?.user ? (
          <a href="#" onClick={handleLogout}>
            Logout
          </a>
        ) : (
          <a href="#" onClick={handleLogin}>
            Login
          </a>
        )}
      </>
      <Link href="/protected">Protected &rarr;</Link>
    </div>
  );
}
  1. Finally we need a /pages/protected page which will show either the logged-in users' information or else it'll say Access Denied. Paste the following code.

pages/protected.js

import styles from '../styles/Home.module.css';
import Link from 'next/link';
import { useSession } from 'next-auth/client';
 
export default function Protected() {
  const [session] = useSession();
  return (
    <div className={styles.container}>
      <Link href="/">&larr; Home</Link>
      <img
        style={{ marginTop: '10px', borderRadius: '400px' }}
        src={session?.user.image}
      />
      {session ? (
        <>
          <h1>Hey {session?.login}!!</h1>
          <Link href="/api/auth/session">
            Want to view your full JSON Session? Click here &rarr;
          </Link>
        </>
      ) : (
        <h1> Access Denied! Please go back and Login</h1>
      )}
    </div>
  );
}
  1. Next, we need to add the code in for the Callbacks and being able to handle Sign in With Apple in our Next App. We'll need to create a new file called /pages/api/[...nextauth.js] which is a catch-all and will handle all the routes which essentially have auth related tasks. You can use the API as you wish and add as many other custom routes as you want as well. Paste the following code.

[...nextauth.js]

import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
 
const providers = [
  Providers.Apple({
    clientId: process.env.APPLE_ID,
    clientSecret: {
      appleId: process.env.APPLE_ID,
      teamId: process.env.APPLE_TEAM_ID,
      privateKey: process.env.APPLE_PRIVATE_KEY,
      keyId: process.env.APPLE_KEY_ID
    }
  })
];
 
const callbacks = {};
 
callbacks.session = async (session, user, sessionToken) => {
  return Promise.resolve({ ...session, ...user, ...sessionToken });
};
 
const options = {
  providers,
  session: {
    jwt: true
  },
  jwt: {
    secret: process.env.JWT_SECRET
  },
  callbacks
};
 
export default (req, res) => NextAuth(req, res, options);

So what we're doing here is, first we're adding an Apple Provider and passing in all the relevant information from our Developer account. We'll talk about adding these fields here shortly.

The next important part is callbacks.session. Essentially callbacks.session is a special function that NextAuth offers us. It allows us to customize what session object the client will see when it calls the useSession hook. All I'm doing is concatenating all of the fields in the session token, and concatenating them into one object. You can customize that however you want and in whatever structure you want.

Finally, make sure you have the same options as I do because we must have jwt mode enabled, and that we're signing our token with our secret. This secret can be whatever you want.

Setting up the .env file

Hope you're still hanging on with me, we're almost there. Finally, we want to create a .env.local file at the root of our project.

.env.local

NEXTAUTH_URL=#ngrok url
JWT_SECRET=
APPLE_ID=
APPLE_TEAM_ID=
APPLE_KEY_ID=
APPLE_PRIVATE_KEY=

So firstly the NEXTAUTH_URL is the root of our application and the URL our app is running at. For each environment, you'll want to put in your base URL. Normally I would put in http://localhost:3000 but I can't do so for Sign In with Apple Because Apple Requires a TLS1.2 or higher. We need to use HTTPS instead of HTTP. There are a few ways to get this working, but the easiest way I've found is with ngrok. This is a utility that ports over your localhost to a public endpoint which you can use for localhost testing and also comply with the https requirement.

JWT_SECRET- Is any random secret used to sign your JWT token, this doesn't matter as long as it's consistent

APPLE_ID- This is the Service ID we created earlier for Sign In with Apple here →

APPLE_TEAM_ID - This is the TEAM ID you see here →

APPLE_KEY_ID and APPLE_PRIVATE_KEY we'll get to soon. Or you can skip to that section here

Install Ngrok Here → https://ngrok.com/download

Once you have ngrok setup, go into terminal, and let's pipe in Port 3000 (the default port that a Next JS App uses) to Ngrok.

Run the command ngrok http 3000 in your terminal. This will start tunneling your app and you'll get a URL. Make sure to keep this terminal window running. If you're not already, run the NextJS app with yarn dev

Confirm that your NextJS app shows up when you click the Forwarding Ngrok URL in your Terminal.

So my current Domain/Subdomain is cdbecf6d8955.ngrok.io

and my Callback URL is

https://cdbecf6d8955.ngrok.io/api/auth/callback/apple

Make sure you have these URLs right (there is no https in the domain/subdomain URL and there IS an https in the Callback URL. Apple does not make this clear, and I was stuck on this step for a few hours.

We'll need these URLs now when we go back to configure our service ID with Apple.

Setting up the Key File

https://cdbecf6d8955.ngrok.io/api/auth/callback/apple
  • You can tie this in with an App ID if you want. Click Done, and then Continue, and Save.

OK, WE'RE ALMOST DONE. WOOH. So now the Final Step is to generate the Key. We need to generate the Key so we can generate our JWT Token. Essentially this is how Apple knows our app is who we say it is.

  • Go to this URL.

  • Click ➕ on Keys

  • Name the Key Whatever you want and make sure to select Sign In With Apple and then click Configure.

  • Tie it back into the Primary App ID we made way earlier in Step 4 of the Apple Dev accounts setup.

  • Register the new key.

  • Now Download this key and make sure to keep it safe. I'll share my key here, but by the time you see this, I'll have revoked access to this key. Also make sure to take note of the Key ID, as we'll need that in our config profile.

  • Finally we need to put this key into our .env.local file, so we need to convert it into a single line env variable

You can convert your Apple key to a single line to use it in an environment variable. Just run the command in the same folder as your auth key.

Mac

awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' AuthKey_ID.k8 <- (replace with your auth key name)

Windows

$k8file = "AuthKey_ID.k8"
(Get-Content "C:\Users\$env:UserName\Downloads\${k8file}") -join "\n"
-----BEGIN PRIVATE KEY-----\< You Key Here>-----END PRIVATE KEY-----\n

Now Finally let's add in our last 2 ENV variables

APPLE_KEY_ID - The ID when we downloaded the Key

APPLE_PRIVATE_KEY The single line Env variable we just made

And That's it. If you've done everything right, at this point, if you go to http://localhost:3000 or your current running ngrok port and try to log in, you'll be redirected to Apple's sign-in page. You can log in and see all the information on the /protected page.