Server SDK (Node.js)

Use the PlainKey Server SDK to verify authentication tokens and manage users and passkeys in your Node.js backend.

Author: PlainKey

← Back to server setup alternatives

In your backend, you must verify authentication tokens. The Server SDK is recommended for Node.js projects. This includes if your backend is built with Express, Hono, Fastify, Next.js, Nuxt, SvelteKit etc.

The Server SDK also allows you to manage users and passkeys.

Installation

In the terminal:

npm install @plainkey/server

Initialise

It is recommended to initialise the PlainKey Server SDK in a single location and import it wherever you need it.

This is because the SDK handles access token management internally. If you were to initialise it on every request, the SDK would fetch a new access token on each request.

Having it in a single location will make it fetch new access tokens only when needed, which will make the Server SDK more efficient.

// server/plainKeyServer.ts or similar
import { PlainKeyServer } from "@plainkey/server"

export const plainKeyServer = new PlainKeyServer("YOUR_PROJECT_ID", "YOUR_CLIENT_SECRET")

Verify authentication token

A user is authenticated in the frontend using the Browser SDK. Authentication occurs when the user authenticates with a passkey, but also when a user is first created with a passkey, or when a user adds a new passkey.

In your backend, build an endpoint that receives the authentication token and exchanges it for a user session or whatever fits your application. You use the PlainKey Server SDK's .verifyAuthenticationToken() method to verify the token.

On verification success, the SDK returns the user's PlainKey User ID, which you use to find or create the user in your database.

You must store this user ID with your user record. This is your primary reference to the PlainKey user.

After verification and storing the user ID, you continue doing what suits your application. For sign-in, this usually involves creating your own session for the user and returning the session data to the frontend.

Endpoint example: Exchanging authentication token for user session

This shows an endpoint that receives the authentication token and exchanges it for a user login session. The endpoint is used for both sign-in and sign-up.

/**
 * Endpoint handler that receives authentication token and exchanges it for a user session
 */
app.post("/api/auth/exchange", async (request, response) => {
  // In this example, we pass in the authentication token, but also optionally a userName for first signup.
  const { token, userName } = request.body

  if (!token) {
    return response.status(400)
  }

  // 1. Verify authentication token with PlainKey.
  //    On success it returns the PlainKey user ID.
  //    An invalid or expired token is a normal outcome. Always check success.
  const { success, data, error } = await plainKeyServer.verifyAuthenticationToken(token)

  if (!success) {
    return response.status(401).json({ error: error?.message })
  }

  const plainKeyUserId = data?.userId

  // 2. Find or create user in your database
  let user = await findUserByPlainKeyId(plainKeyUserId)

  if (!user) {
    // First time we've seen this user (signup).
    // In this case we'd usually have passed a userName or some identity information to the endpoint.
    if (!userName) {
      return response.status(400)
    }

    user = await createUser({
      plainKeyUserId,
      userName
    })
  }

  // 3. For sign-in: Establish a session for the user
  //    (typically by creating a session and setting a first-party cookie)
  await establishUserSession(response, user.id)

  response.json({ success: true })
})

Manage users

// Get a user by their PlainKey user ID
const user = await plainKeyServer.getUser("user-id")

// Find a user by userName
const user = await plainKeyServer.findUser("user@example.com")

// Create a user
const user = await plainKeyServer.createUser("user@example.com")

// Update a user's userName. Pass null to clear it.
const user = await plainKeyServer.updateUser({ userId: "user-id" }, { userName: "new@example.com" })

// Delete a user and all their passkeys
await plainKeyServer.deleteUser({ userId: "user-id" })

Users can be identified by their PlainKey user ID or their userName:

// By PlainKey user ID
{
  userId: "user-id"
}

// By userName
{
  userName: "user@example.com"
}

Bulk create users

Useful for migrating existing users to PlainKey. Pass an array of userNames.

const users = await plainKeyServer.bulkCreateUsers(["user1@example.com", "user2@example.com"])

Manage passkeys

// Get all passkeys for a user
const passkeys = await plainKeyServer.getUserCredentials("user-id")

// Get a specific passkey
const passkey = await plainKeyServer.getCredential("credential-id")

// Delete a passkey
await plainKeyServer.deleteCredential("credential-id")

// Update a passkey's label. Pass null to clear it.
await plainKeyServer.updateCredentialLabel("credential-id", "My security key")

Server-initiated passkey registration

You can start a passkey registration from your backend without the user authenticating with PlainKey first.

This is useful when you already know who the user is. For example, prompting an already signed-in user to add a passkey, or sending existing users an email to register one.

const { options, authenticationToken } = await plainKeyServer.beginCredentialRegistration({
  userId: "user-id"
})

Pass both options and authenticationToken to your frontend, then call .completePasskeyRegistration() from the Browser SDK:

// In your frontend
const { success, error } = await plainKey.completePasskeyRegistration(authenticationToken, options)