This quick start guide will walk you through how to quickly get setup with the Passport SDK with Clerky account, register and authenticate users, and start signing messages and transactions. You can skip to the Complete Setup, and then follow the section step-by-step where each code snippet will be explained. You can also quickly pull the completed repository here https://github.com/0xpass/passport-clerk-quickstart and follow along.
Project setup
First we'll setup a Next.js project, with tailwind, we can do this with the following command, and walk through the configuration wizard, making sure to chose the app-router, and tailwind.
npx create-next-app@latest
Setup and Dependencies
Firstly, we'll ensure we have all the requires dependencies. So we're going to install the Passport SDK @0xpass/passport to interact with Passport protocol, and @clerk/nextjs for third party authentication. We'll also install some helper packages @0xpass/key-signer to handle our signature management for delegated registration and authentication.
You should grab your own NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, and CLERK_SECRET_KEY from your Clerk account.
For Developer Key (PRIVATE_KEY), follow Generating DOA Keys for generating your own keys. We'll refer to the private key generated in this step as PRIVATE_KEY environment variable in the example below.
Backend APIs
Firstly let's setup backend API for creating an account in passport protocol upon successful authentication via Clerk.
Authentication Flow:
Signing Flow:
This is not a part of backend, but you need a page that takes the callback from clerk, and redirects to /api/user-callback. In app/auth/callback we'll have the following snippet so that frontend calls the backend endpoint with user id and email address upon successful sign-in.
We'll create a file at:app/api/user-callback/route.ts Which will create a Passport instance with KeySigner with your private key. PRIVATE_KEY environment variable can point to a file path or the actual private key. Note that the private key should only be handled in your backend, and can't be exposed publicly.
import { NextResponse } from "next/server";
import { KeySigner } from "@0xpass/key-signer";
import { Passport } from "@0xpass/passport";
export async function POST(req: Request) {
const payload = await req.json();
const keySigner = new KeySigner(process.env.PRIVATE_KEY!, true);
const passport = new Passport({
scope_id: process.env.NEXT_PUBLIC_SCOPE_ID!,
signer: keySigner,
});
await passport.setupEncryption();
const data = await passport.delegatedRegisterAccount({
username: payload.emailAddress,
});
return NextResponse.json(data);
}
After authenticating with Clerk, you can directly interact with Passport with the username (email from Clerk) and your developer keys.
Now we'll implement get-accountendpioint at app/api/get-account/route.ts. For retrieving your user's email address you can use currentUser() function which will grab user information from the session established with Clerk.
import { currentUser } from "@clerk/nextjs/server";
import { KeySigner } from "@0xpass/key-signer";
import { Passport } from "@0xpass/passport";
export async function GET() {
try {
const user = await currentUser();
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const keySigner = new KeySigner(process.env.PRIVATE_KEY!, true);
const passport = new Passport({
scope_id: process.env.NEXT_PUBLIC_SCOPE_ID!,
signer: keySigner,
});
passport.setUserData({ username: user.emailAddresses[0].emailAddress });
await passport.setupEncryption();
const addresses = await passport.getAddresses();
return new Response(JSON.stringify(addresses), { status: 200 });
} catch (error) {
console.log(error);
return new Response("Something went wrong", { status: 500 });
}
}
Lastly, we're using signing message as an example of user action. In app/api/sign/route.ts, implement the following function.
import { NextResponse } from "next/server";
import { currentUser } from "@clerk/nextjs/server";
import { KeySigner } from "@0xpass/key-signer";
import { Passport } from "@0xpass/passport";
import { stringToHex } from "viem";
export async function POST(req: Request) {
const payload = await req.json();
const { type, data } = payload;
const user = await currentUser();
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
const keySigner = new KeySigner(process.env.PRIVATE_KEY!, true);
const passport = new Passport({
scope_id: process.env.NEXT_PUBLIC_SCOPE_ID!,
signer: keySigner,
});
passport.setUserData({ username: user.emailAddresses[0].emailAddress });
await passport.setupEncryption();
const signature = await passport.signMessage(stringToHex(data));
return NextResponse.json({ signature });
}
Middleware
Under app directory, we need a middleware to keep the authenticated user context across the backend apis and the frontend.
First we'll need to make sure we wrap our application inside the ClerkProvider . We can do this by creating a new file providers.tsx inside the src directory, and populating it as below.
"use client";
import { ClerkProvider } from "@clerk/nextjs";
export function Providers({ children }: { children: JSX.Element }) {
return <ClerkProvider>{children}</ClerkProvider>;
}
Then we can now wrap our app with our Provider by going to src/layout.tsx and adjusting it so it looks like this
Now with all of this setup we can setup our UI too, so the overall setup looks as below. In the final part of our setup, we've created the UI layer to interact with the above functions. We have buttons to register and authenticate, which dynamically interact with the state, to show loading states, as well as authenticated states, and finally when a message is signed, the user is also able to see the signed message signature.
Please refer to https://github.com/0xpass/passport-clerk-quickstart for the complete setup.
You should be able to clone, update environment variable and run the example in less than a minute.