Add "Login with Passkeys" to your Next.js app in <2 mins (Page Router Mode)
Tags: nextjs, passkeys, authjs, react
Published: 2024-05-28 17:04:36

Passkeys, passkeys, passkeys! Everyone's talking about them. With [Amazon](https://www.theverge.com/2023/10/23/23928589/amazon-passkey-support-web-ios-shopping-mobile-app?ref=pangea.cloud) rolling out passkeys last year and [Google](https://blog.google/technology/safety-security/passkeys-default-google-accounts/?ref=pangea.cloud) encouraging users to make them the default authentication method, it raises the question: How do I add them to my app?

## How do passkeys work?
If you’re just interested in implementing them, you can skip this section. No offense taken 😉

FIDO2 multidevice credentials more often referred to as “passkeys” is a standard introduced by the FIDO alliance. It’s an extremely powerful way of using public-key cryptography to verify the identity of a user without passwords, multi-factor, etc. The public / private key pair is usually generated and securely stored on a hardware or software authenticator device.

To learn more about how passkeys and authenticators work in detail, check out the [Secure By Design Hub](https://pangea.cloud/securebydesign/authn-using-passkeys/?utm_source=devto&utm_medium=passkeys-blog-nextjs) article on passkeys.

## How do I build it in?
In this tutorial, we’re going to leverage Pangea AuthN’s hosted pages to be able to quickly configure passkeys without building all the cryptographic mayhem from scratch 😅. To prove that it’s easy to add passkeys into any application in just a few minutes, I’m going to start with a fresh new Next.js app and implement passkeys in just a few steps.

### Step 1: Create a new NextJS App and install Pangea AuthN react wrapper
```bash
npx create-next-app --ts --eslint --tailwind --src-dir passkeys-demo
cd passkeys-demo
npm i @pangeacyber/react-auth
```
_Note: Select "No" when the create-next-app command asks if you want to use the `App Router` mode. In this tutorial, we will be using the Next.js page router mode._

### Step 2: Create an account on [pangea.cloud](https://l.pangea.cloud/acJTkqo)
Head over to pangea.cloud and create an account for free. Then in the developer console, enable the “AuthN” service and grab the following tokens. These tokens will be pasted in your .env config file as shown below.

```bash
NEXT_APP_LOGIN_URL="<PROJECT_HOSTED_LOGIN_URL>"
NEXT_APP_CLIENT_TOKEN="<PROJECT_CLIENT_TOKEN>"
NEXT_APP_PANGEA_DOMAIN="<PANGEA_DOMAIN>"
```

### Step 3: Add the Pangea React Auth components to the new Next app (Page Router Mode)
Now we edit our `_app.js` file with the `<AuthProvider>` component that maintains authentication context and state across the application.

So our `_app.js` file should look like this:
```ts
import "@/styles/globals.css";
import type { AppProps } from "next/app";
// Pangea imports
import { AuthProvider } from "@pangeacyber/react-auth";

export default function App({ Component, pageProps }: AppProps) {
  // Setup props with Pangea AuthN config
  const hostedLoginURL = process?.env?.NEXT_PUBLIC_AUTHN_HOSTED_LOGIN_URL || "";
  const authConfig = {
    clientToken: process?.env?.NEXT_PUBLIC_AUTHN_CLIENT_TOKEN || "",
    domain: process?.env?.NEXT_PUBLIC_PANGEA_DOMAIN || "",
  };

  return (
    <AuthProvider loginUrl={hostedLoginURL} config={authConfig}>
      <Component {...pageProps} />
    </AuthProvider>
  );
}
```

Now that we have the `AuthProvider` keeping context across the application, we can now implement the login functionality by simply calling the useAuth hook in our `index.js` file. Let’s also add a conditional redirect in a `useEffect` hook to automatically redirect a user if they’re logged in to a page called `/login-success` that we will create later.

Thus your `index.js` file should look like this:

```ts
// Pangea AuthN imports
import { useAuth } from "@pangeacyber/react-auth";
import { useEffect } from "react";
import { useRouter } from "next/router";

export default function Home() {
  const { authenticated, user, login, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (authenticated && !loading) {
      // Redirect to /login-success if user is authenticated
      router.push("/login-success");
    }
  }, [user, authenticated, loading])

  return (
    <div className="flex flex-col items-center justify-center h-screen bg-gradient-to-br from-[#8b5cf6] to-[#a855f7]">
      <h1 className="text-4xl font-bold text-white mb-6">Login with Passkeys 🔑</h1>
      <button
        className="inline-flex items-center justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-[#8b5cf6] shadow-sm transition-colors hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-[#8b5cf6] focus:ring-offset-2 mb-6"
        onClick={login}
      >
        Login
      </button>
      <a
            className="text-xl font-light text-blue-300 underline"
            href="https://pangea.cloud/services/authn/?utm_source=blog&utm_medium=passkeys-nextjs-snippet"
            target="_blank"
            rel="noopener noreferrer"
          >
            Try Pangea AuthN for free today!
      </a>
    </div>
  );
}
```
Now that we have our login page, let’s define an authenticated page called `login-success.tsx` by using the `useAuth` hook to get user information and display it.

Thus your `login-success.tsx` file should look like this:

```ts
// Pangea AuthN imports
import { useAuth } from "@pangeacyber/react-auth";
import { useEffect } from "react";
import { useRouter } from "next/router";

export default function Home() {
  const { authenticated, user, logout, loading, getToken } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (!authenticated && !loading) {
      // Redirect to / if user is NOT authenticated
      router.push("/");
    }
  }, [user, authenticated, loading])

  return (
    <div className="flex flex-col h-screen w-full items-center justify-center bg-gradient-to-br from-[#7e22ce] to-[#a855f7]">
      <div className="rounded-lg bg-white p-8 shadow-lg w-full max-w-md">
        <div className="flex items-center justify-between mb-6">
          <div className="bg-gray-100 px-3 py-1 rounded-full text-sm text-gray-500">hi {user?.email}</div>
          <button
        className="inline-flex items-center justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-[#8b5cf6] shadow-sm transition-colors hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-[#8b5cf6] focus:ring-offset-2 mb-6" onClick={logout}>
            Sign Out
            </button>
        </div>

        <div className="flex items-center justify-start g">
          {/* Fun lil GIF */}
          <div className="flex flex-col"><iframe src="https://giphy.com/embed/IwAZ6dvvvaTtdI8SD5" width="480" height="400" frameBorder="0" className="h-3/4 w-3/4" allowFullScreen></iframe><p className="text-xs"><a href="https://giphy.com/gifs/theoffice-the-office-tv-michaels-birthday-IwAZ6dvvvaTtdI8SD5">via GIPHY</a></p></div>

          {/* Access user's information through user variable */}
          <p className="flex-none text-[#8b5cf6]">User Name: {user?.profile.first_name} {user?.profile.last_name}</p> 
        </div>
      </div>
      <div className="space-y-4 mt-6">
          <div className="flex items-center space-x-4">
          <a
            className="text-xl font-light text-blue-300 underline"
            href="https://pangea.cloud/services/authn/?utm_source=blog&utm_medium=passkeys-nextjs-snippet"
            target="_blank"
            rel="noopener noreferrer"
          >
            Try Pangea AuthN for free today!
          </a>
          </div>
      </div>
    </div>
  );
}
```

### Step 4: Enable Passkeys Authentication in Pangea console
Since we have a `/login-success` page to redirect to on successful authentication, we need to add an authorized redirect so that Pangea’s AuthN hosted pages can securely redirect a user back to the `http://localhost:3000/login-success` when done authenticating.

So first, let’s go under `General > Redirect (Callback) Settings` and add `http://localhost:3000/*` as a redirect and save it. This will allow redirects to all routes on our host `http://localhost:3000`.

Here comes the last step, let’s enable Passkeys! Head over to Single Sign On > Passkeys and enable it. Optionally you can choose to enable fallback authentication options based on your desired user experience.

Here’s a quick video on how you can enable it in settings:
{% embed https://www.youtube.com/watch?v=M2kPx1WteEE %}

Let’s test it out! You can find the code in [Github](https://github.com/pangeacyber/pangea-integration-nextjs-authn) and watch the demo below.
{% embed https://www.youtube.com/watch?v=WcoEiqsSRuE %}
---

If you are looking to add Pangea AuthN to other languages or frameworks, follow these tutorials:
* [React.js](https://pangea.cloud/blog/add-passkeys-to-reactjs-in-2mins/?utm_source=devto&utm_medium=nextjs-passkeys-blog)
* [Python Django](https://pangea.cloud/blog/add-login-with-passkeys-to-your-django-app/?utm_source=devto&utm_medium=nextjs-passkeys-blog)