Passkeys, passkeys, passkeys! Everyone's talking about them. With Amazon rolling out passkeys last year and Google 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 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
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 theApp Router
mode. In this tutorial, we will be using the Next.js page router mode.
Step 2: Create an account on pangea.cloud
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.
NEXT_PUBLIC_AUTHN_HOSTED_LOGIN_URL="<PROJECT_HOSTED_LOGIN_URL>"
NEXT_PUBLIC_AUTHN_CLIENT_TOKEN="<PROJECT_CLIENT_TOKEN>"
NEXT_PUBLIC_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:
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:
// 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:
// 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 it as 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 URL 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:
Let’s test it out! You can find the code in Github and watch the demo below.
If you are looking to add Pangea AuthN to other languages or frameworks, follow these tutorials: