Skip to content

Next.js API Route Reference

Installation

Next.js API routes support express compatible middleware, so we will use the @propelauth/express library.

$ npm install --save @propelauth/express
$ yarn add @propelauth/express

Initialize

In lib/propelauth.js:

import {initAuth} from "@propelauth/express";

const propelauth = initAuth({
    debugMode: true, // (1)
    authUrl: "REPLACE_ME", // (2)
    apiKey: "REPLACE_ME", // (3)
    manualTokenVerificationMetadata: { 
        verifierKey: "REPLACE_ME", // (4)
        issuer: "REPLACE_ME", // (5) 
    }
})
export default propelauth
  1. If true, error messages returned to the user will be detailed. It's useful for debugging, but a good idea to turn off in production.
  2. The base URL where your authentication pages are hosted. You can find this under the Backend Integration section for your project at https://app.propelauth.com.
  3. You can manage your api keys under the Backend Integration section for your project.
  4. You can find this under the Backend Integration section for your project at https://app.propelauth.com.
  5. You can find this under the Backend Integration section for your project at https://app.propelauth.com.

You can then import what you need from propelauth in your routes:

import {
    requireUser,
    optionalUser,
    requireOrgMember,
    requireOrgMemberWithMinimumRole,
    requireOrgMemberWithExactRole,
    requireOrgMemberWithPermission,
    requireOrgMemberWithAllPermissions,
    fetchUserMetadataByUserId,
    fetchUserMetadataByEmail,
    fetchUserMetadataByUsername,
    fetchBatchUserMetadataByUserIds,
    fetchBatchUserMetadataByEmails,
    fetchBatchUserMetadataByUsernames,
    fetchOrg,
    fetchOrgByQuery,
    fetchUsersByQuery,
    fetchUsersInOrg,
    createUser,
    updateUserMetadata,
    updateUserEmail,
    createMagicLink,
    migrateUserFromExternalSource,
    createOrg,
    addUserToOrg,
    deleteUser,
    disableUser,
    enableUser,
    allowOrgToSetupSamlConnection,
    disallowOrgToSetupSamlConnection,
} from "../../lib/propelauth"

Running middleware in a NextJS API route

The full details are available in the official docs.

Nevertheless, an important thing to know is that you can create this helper function:

// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
export function runMiddleware(req, res, fn) {
    return new Promise((resolve, reject) => {
        fn(req, res, (result) => {
            if (result instanceof Error) {
                return reject(result)
            }

            return resolve(result)
        })
    })
}

And then use that to call middleware within your routes:

async function handler(req, res) {
    // Run the middleware
    await runMiddleware(req, res, YOUR_MIDDLEWARE)

    // Rest of the API logic
    res.json({message: 'Hello Everyone!'})
}

Protect API routes

requireUser

An Express middleware that will verify the request was made by a valid user. Specifically, it will:

  1. Check that a valid access token was provided. If not, the request is rejected with a 401 status code.
  2. Set user on the Request with the user's information.
import {requireUser} from "../../lib/propelauth"

async function handler(req, res) {
    // Verifies that a request was made with a valid access token
    await runMiddleware(req, res, requireUser)

    // req.user is now set from our requireUser middleware
    res.status(200).json({message: "Hello user with ID " + req.user.userId});
}

The user object looks like:

Property Description
userId The id of the user
orgIdToOrgMemberInfo A dictionary of org ids to metadata about the org. Includes all orgs that the user is in
legacyUserId The original ID of the user, if the user was migrated from an external source

The values of orgIdToOrgMemberInfo are OrgMemberInfo's, with the following fields/functions:

OrgMemberInfo fields/properties Description
orgId The id of the org
orgName The name of the org
urlSafeOrgName A URL-safe version of the name of the org
assignedRole(): string The user's role within the organization. See Roles and Permissions for more details.
permissions(): string[] The user's permissions within the organization. See Roles and Permissions for more details.
isRole(role: string): boolean Returns true if the user's role within the organization matches the role passed in
isAtLeastRole(role: string): boolean Returns true if the user's role within the organization is at least the role passed in. If the hierarchy of roles is Owner => Admin => Member, specifying "Admin" will return True for Admins and Owners, but False for Members.
hasPermission(permission: string): boolean Return true if the user has a specific permission. The users' permissions are derived from their role within this organization.
hasAllPermissions(permissions: string[]): boolean Return true if the user has all the specified permissions. The users' permissions are derived from their role within this organization.

optionalUser

Similar to requireUser, except if an access token is missing or invalid, the request is allowed to continue, but user on the Request will be undefined.

import {optionalUser} from "../../lib/propelauth"

async function handler(req, res) {
    await runMiddleware(req, res, optionalUser)

    if (req.user) {
        res.status(200).json({message: "Hello user with ID " + req.user.userId});
    } else {
        res.status(200).json({message: "Hello anonymous"})
    }
}

requireOrgMember

An Express middleware that will verify the request was made by a valid user that belongs to a specific organization.

import {requireOrgMember} from "../../lib/propelauth"

const requireUserInOrgByQueryParam = requireOrgMember({
    // Tell our middleware that the orgId comes from `req.query`
    orgIdExtractor: (req) => req.query.orgId,
})

async function handler(req, res) {
    await runMiddleware(req, res, requireUserInOrgByQueryParam)

    // requireAdminInOrg automatically sets req.org
    res.status(200).json(
        {message: "You are in " + req.org.orgName}
    );
}

Specifically, it will:

  1. Check that a valid access token was provided. If not, the request is rejected with a 401 status code.
  2. Set user on the Request with the user's information. See requireUser for what the user object looks like.
  3. Find an id for an organization within the request. By default, it will check Request.params.orgId (a path parameter named orgId).
  4. Check that the user is a member of that organization. If not, the request is rejected with a 403 status code.
  5. Set org on the Request with the organization's information for this user.

Roles and Permissions

A user has a Role within an organization. By default, the available roles are Owner, Admin, or Member, but these can be configured. These roles are also hierarchical, so Owner > Admin > Member.

Roles allow you to control what different users can do within your product. If you want to check a user's role, you can use requireOrgMemberWithExactRole or requireOrgMemberWithMinimumRole.

// Assuming a Role structure of Owner => Admin => Member
async function handler(req, res) {
    await runMiddleware(req, res, requireOrgMemberWithExactRole({role: "Admin"}))

    res.status(200).json(
        {message: "You are in an Admin of " + req.org.orgName}
    )
}
async function handler(req, res) {
    await runMiddleware(req, res, requireOrgMemberWithMinimumRole({minimumRequiredRole: "Admin"}))

    res.status(200).json(
        {message: "You are in an Admin or Owner of " + req.org.orgName}
    )
}

Permissions are arbitrary strings associated with a role. For example, can_view_billing, ProductA::CanCreate, and ReadOnly are all valid permissions. The PropelAuth dashboard allows you to set up these permissions.

You can use requireOrgMemberWithPermission or requireOrgMemberWithAllPermissions to check for a given permission.

async function handler(req, res) {
    await runMiddleware(req, res, requireOrgMemberWithPermission({permission: "can_view_billing"}))

    // view billing
}

All of these functions, just like requireOrgMember, can also take in orgIdExtractor, and will set both req.user and req.org on a valid request.

Fetch user metadata by id

function fetchUserMetadataByUserId(userId: string, includeOrgs?: boolean): Promise<UserMetadata | null>

// Example
async function handler(req, res) {
    // Verifies that a request was made with a valid access token
    await runMiddleware(req, res, requireUser)

    // Fetch and return latest user metadata for this user from PropelAuth
    const userMetadata = await fetchUserMetadataByUserId(req.user.userId)
    res.status(200).json({"userId": userMetadata.userId, "email": userMetadata.email, "lastActiveAt": userMetadata.lastActiveAt});
}

Successful response (UserMetadata):

{
    userId:            "e9d3520f-836e-403c-82c2-09843517e1ce", 
    email:             "user@example.com",
    emailConfirmed:    true,
    hasPassword:       true,

    username:          "example", // (1)
    firstName:         "first", // (2)
    lastName:          "last", // (3)
    pictureUrl:        "https://...", // (4)

    locked:            false, // (5)
    enabled:           true, 
    mfaEnabled:        false, 

    createdAt:         1645131680,
    lastActiveAt:      1650654711,

    // Only returned if includeOrgs = true
    orgIdToOrgInfo: { // (6)
        "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4" : {
            orgId: "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4", 
            orgName: "ExampleOrganization", 
            userRole: "Owner",
        }
    },

    // Only returned if user was migrated from an external system 
    legacyUserId:    "507f191e810c19729de860ea"
}
  1. Optional: Only returned if you have the field enabled as part of your user schema
  2. Optional: Only returned if you have the field enabled as part of your user schema
  3. Optional: Only returned if you have the field enabled as part of your user schema
  4. Optional: Only returned if you have the field enabled as part of your user schema
  5. True if the user's account is locked for security purposes.
  6. Includes all organizations that the user is a member of

Fetch user metadata by email

function fetchUserMetadataByEmail(email: string, includeOrgs?: boolean): Promise<UserMetadata | null>

// Example
const email = await db.fetchEmailFromAnalytics()
const userMetadata = await fetchUserMetadataByEmail(req.email)
if (userMetadata) {
    console.log("Found user " + userMetadata)
} else {
    console.log("No user found")
}

Successful response is the same as Fetch user metadata by id.

Fetch user metadata by username

function fetchUserMetadataByUsername(username: string, includeOrgs?: boolean): Promise<UserMetadata | null>

// Example
const userMetadata = await fetchUserMetadataByUsername(req.query.username);
res.status(200).json({"userId": userMetadata.userId, "pictureUrl": userMetadata.pictureUrl});

Successful response is the same as Fetch user metadata by id.

Batch fetch users by ids

function fetchBatchUserMetadataByUserIds(userIds: string[], includeOrgs?: boolean): Promise<{ [userId: string]: UserMetadata }>

Successful response:

Any IDs that don't have a match are not returned. Duplicate users are only returned once.

{
    "e9d3520f-836e-403c-82c2-09843517e1ce": {
        userId:            "e9d3520f-836e-403c-82c2-09843517e1ce",
        email:             "user@example.com",
        emailConfirmed:    true,
        hasPassword:       true,

        username:          "example", // (1)
        firstName:         "first", // (2)
        lastName:          "last", // (3)
        pictureUrl:        "https://...", // (4)

        locked:            false, // (5)
        enabled:           true,
        mfaEnabled:        false,

        createdAt:         1645131680,
        lastActiveAt:      1650654711,

        // Only returned if includeOrgs = true
        orgIdToOrgInfo: { // (6)
            "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4" : {
                orgId: "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4",
                    orgName: "ExampleOrganization",
                    userRole: "Owner",
            }
        },

        // Only returned if user was migrated from an external system 
        legacyUserId: "507f191e810c19729de860ea"
    },
    // other user_ids
}
  1. Optional: Only returned if you have the field enabled as part of your user schema
  2. Optional: Only returned if you have the field enabled as part of your user schema
  3. Optional: Only returned if you have the field enabled as part of your user schema
  4. Optional: Only returned if you have the field enabled as part of your user schema
  5. True if the user's account is locked for security purposes.
  6. Includes all organizations that the user is a member of

Batch fetch users by emails

function fetchBatchUserMetadataByEmails(emails: string[], includeOrgs?: boolean): Promise<{ [email: string]: UserMetadata }> 

Successful response is the same as Batch fetch users by ids, but the keys are matching email addresses.

Batch fetch users by usernames

function fetchBatchUserMetadataByUsernames(usernames: string[], includeOrgs?: boolean): Promise<{ [username: string]: UserMetadata }>

Successful response is the same as Batch fetch users by ids, but the keys are matching usernames.

Search for users

function fetchUsersByQuery(usersQuery: UsersQuery): Promise<UsersPagedResponse>

// Example
const usersResponse = await auth.fetchUsersByQuery({emailOrUsername: "@example.com", orderBy: "LAST_ACTIVE_AT_DESC"})
for (const user of userResponse.users) {
    console.log("Found user " + user)
}
Argument Description
pageSize The number of results to return at a time. Must be between 1 and 100, default 10.
pageNumber Used to page over results
orderBy How to order the results. Must be one of: CREATED_AT_ASC, CREATED_AT_DESC, LAST_ACTIVE_AT_ASC, LAST_ACTIVE_AT_DESC, EMAIL, USERNAME
emailOrUsername Searches for partial matches within email addresses or usernames. port would match a user with email address support@propelauth.com.
includeOrgs Whether or not to include the user's organization information in the response. Default false

Successful response:

{
    "totalUsers": 103, 
    "currentPage": 0, 
    "pageSize": 10, 
    "hasMoreResults": true,
    "users": [{
        // See (1) for example users
    }, {
        // There will be 10 users in this response
    }]
}
  1. Example user in response

Fetch users in organization

function fetchUsersInOrg(usersInOrgQuery: UsersInOrgQuery): Promise<UsersPagedResponse>
Argument Description
orgId The organization to fetch users for
pageSize The number of results to return at a time. Must be between 1 and 100, default 10.
pageNumber Used to page over results
includeOrgs Whether or not to include the user's organization information in the response. Default false

Successful response:

{
    "totalUsers": 43, 
    "currentPage": 0, 
    "pageSize": 10, 
    "hasMoreResults": true,
    "users": [{
        // See (1) for example users
    }, {
        // There will be 10 users in this response, all in the specified organization
    }]
}
  1. Example user in response

Create User

function createUser(createUserRequest: CreateUserRequest): Promise<User>
Argument Description
email The user's email address
emailConfirmed Whether the email address should start off as already confirmed. If false, the user is required to confirm the email address before they sign in.
sendEmailToConfirmEmailAddress Whether we should send an email immediately to confirm the user's email address. If false, the user will get a confirmation email when they first sign in.
password Optional password. If unset, the user can set it themselves via their account page
username Optional username. Can only be used if username is enabled in your user schema
firstName Optional first name. Can only be used if name is enabled in your user schema
lastName Optional last name. Can only be used if name is enabled in your user schema

Successful response:

{
    "userId": "b5f667fb-e51a-49c6-a396-711e62948689"
}

Migrate User from External Source

Similar to Create User, but for cases where you already have a user stored in an external system. You can, for example, pass in a password hash for an existing user, and they will be able to login with their same password. It is also possible to provide an existing identifier, and we will store and return it along with our future identifiers, allowing you to link them.

function migrateUserFromExternalSource(migrateUserFromExternalSourceRequest: MigrateUserFromExternalSourceRequest): Promise<User>
Argument:w
Description
email The user's email address
emailConfirmed Whether the email address should start off as already confirmed. If false, the user is required to confirm the email address before they sign in.
existingUserId (Optional) The user's ID in the existing system. This ID will be stored on the user as a legacyUserId and it is present everywhere userId's are (e.g. fetching user metadata or validating a user's token).
existingPasswordHash (Optional) The user's hashed password. We support both bcrypt and argon2 password hashes.
existingMfaBase32EncodedSecret (Optional) The user's MFA secret, base32 encoded. If specified the user will have MFA enabled by default.
enabled (Optional) Whether or not the user can login
username Optional username. Can only be used if username is enabled in your user schema
firstName Optional first name. Can only be used if name is enabled in your user schema
lastName Optional last name. Can only be used if name is enabled in your user schema

Successful response:

{
    "userId": "b5f667fb-e51a-49c6-a396-711e62948689"
}

Creates a magic link that a user can use to log in. Use this API if you'd prefer to send the magic link to the customer yourself, otherwise, we have createUser which will email them directly.

function createMagicLink(createUserRequest: CreateMagicLinkRequest): Promise<MagicLink>
Argument Description
email The user's email address
redirectToUrl (Optional) Where to redirect the user after they login. If unspecified, will use the login redirect path specified in your dashboard.
expiresInHours (Optional) How many hours should the link be valid for?
createNewUserIfOneDoesntExist (Optional) If true, createMagicLink will create a new user if one matching the provided email address doesn't exist. If false, the request will fail if no user with that email exists. Default is true.

Successful response:

{
    "url": "https://auth.yourdomain.com/..."
}

Update User Email

// Returns true if it was successful, false if the user was not found
function updateUserEmailWrapper(userId: string, updateUserEmailRequest: UpdateUserEmailRequest): Promise<boolean>
Argument Description
newEmail New email address for this user
requireEmailConfirmation Whether the new email address requires confirmation. If true, an email will be sent to the new email address to confirm. If false, the users email address will be updated and confirmed immediately.

Update User Metadata

// Returns true if it was successful, false if the user was not found
function updateUserMetadata(userId: string, updateUserMetadataRequest: UpdateUserMetadataRequest): Promise<boolean>
Argument Description
username Optional username. Can only be used if username is enabled in your user schema
firstName Optional first name. Can only be used if name is enabled in your user schema
lastName Optional last name. Can only be used if name is enabled in your user schema

Delete user

function deleteUser(userId: string): Promise<boolean>

Returns true if the user was deleted.

Disable user

function disableUser(userId: string): Promise<boolean>

Returns true if the user was disabled. Afterwards, the user is logged out and unable to log back in.

Enable user

function enableUser(userId: string): Promise<boolean>

Returns true if the user was enabled, allowing them to log in again.

Fetch an organization

function fetchOrg(orgId: string): Promise<Org | null>

Successful response:

{
    "orgId": "d488996d-8ccc-4101-b5f2-131f5f09ddb6"
    "name": "OneOfYourCustomers"
}

Fetch all organizations

function fetchOrgsByQuery(orgQuery: OrgQuery): Promise<OrgQueryResponse>
Argument Description
pageSize The number of results to return at a time. Must be between 1 and 100, default 10.
pageNumber Used to page over results
orderBy How to order the results. Must be one of: CREATED_AT_ASC, CREATED_AT_DESC, NAME

Successful response:

{
    "totalOrgs": 21, 
    "currentPage": 0, 
    "pageSize": 10, 
    "hasMoreResults": true,
    "orgs": [{
        "orgId": "d488996d-8ccc-4101-b5f2-131f5f09ddb6",
        "name": "OneOfYourCustomers"
    }, {
        // There will be 10 orgs in this response
    }]
}

Create Organization

function createOrg(createOrgRequest: CreateOrgRequest): Promise<Org>
Argument Description
name The organization's name

Successful response:

{
    "orgId": "d488996d-8ccc-4101-b5f2-131f5f09ddb6"
}

Add user to organization

function addUserToOrg(addUserToOrgRequest: AddUserToOrgRequest): Promise<boolean>
Argument Description
userId The user's ID
orgId The org's ID
role The role of the user in that organization, e.g. Admin

Successful response:

{}

Allow organization to set up SAML connection

function allowOrgToSetupSamlConnection(orgId: string): Promise<boolean>

Returns true if successful, meaning the organization can now set up SAML connections.

Disallow organization to set up SAML connection

function disallowOrgToSetupSamlConnection(orgId: string): Promise<boolean>

Returns true if successful, meaning the organization can no longer set up SAML connections.