Integrations
Integrate Multi-Factor Authentication
MFA has two jobs in a product: proving who someone is at sign-in, and proving it again before something irreversible. This page covers enrolment, verification and the step-up check, plus the recovery path that decides whether your support team spends its week resetting factors.
Before you begin
- A signed-in session. Enrolment happens as an authenticated action, never during sign-up from an unauthenticated request.
- A decision about enforcement: optional, required for admins, or required for everyone in an organization.
- A recovery plan. Codes get lost with phones, so either issue backup codes at enrolment or define who may reset a factor.
- A list of the actions that deserve a step-up prompt. Changing an email, moving money, exporting data.
Install
Every example on this page uses the Node.js client. The same calls exist in the Python, Go, Ruby, PHP and Java SDKs, following the naming conventions of each language.
1npm i @paycux/node
Configure
MFA rides on the session you already have. The issuer name below is what appears in the person's authenticator app next to the code, so make it the name of your product.
1PAYCUX_API_KEY=sk_example_7f4c1b9ad2e84c6f2PAYCUX_CLIENT_ID=client_01HQZX8N4T3PAYCUX_MFA_ISSUER=Your Product
Then, in the dashboard:
- 1Open Authentication, then Multi-Factor, and enable the factor types you want: authenticator app, SMS one-time code, or both.
- 2Set the enforcement policy. Requiring MFA for admin roles only is a reasonable first step and does not block sign-up.
- 3Set the issuer name and check how it renders in an authenticator app before you ship it.
- 4Choose whether backup codes are issued automatically at enrolment.
- 5Decide who may reset a factor: an organization admin, your support team, or both, and note that this decision is itself a security boundary.
Implement
Enrolment is three steps in a fixed order: create a factor, show the QR code, then activate it with a code the person types. A factor is not enrolled until it has been challenged successfully once.
Enrol a factor
Create the factor and hand the QR code and secret to the browser. The factor exists but is not yet usable, which is exactly what you want if the person closes the tab halfway through.
1import { Paycux } from "@paycux/node";2import { requireSession } from "@/lib/session";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function POST() {7 const { user } = await requireSession();89 const factor = await paycux.mfa.enrollFactor({10 userId: user.id,11 type: "totp",12 issuer: process.env.PAYCUX_MFA_ISSUER,13 accountName: user.email,14 });1516 return Response.json({17 factorId: factor.id,18 qrCode: factor.totp.qrCode,19 secret: factor.totp.secret,20 });21}
Activate it
Ask for a code from the app and activate on success. If backup codes are enabled they are returned once here — show them, and make clear they will not be shown again.
1import { Paycux } from "@paycux/node";2import { requireSession } from "@/lib/session";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function POST(request: Request) {7 await requireSession();8 const { factorId, code } = await request.json();910 const result = await paycux.mfa.activateFactor({ factorId, code });1112 if (!result.valid) {13 return Response.json({ error: "invalid_code" }, { status: 400 });14 }1516 return Response.json({ ok: true, backupCodes: result.backupCodes });17}
Step up before a sensitive action
A step-up is a fresh challenge, not a check that MFA was used at sign-in an hour ago. Issue a challenge, verify it, and only then perform the action.
1import { Paycux } from "@paycux/node";2import { requireSession } from "@/lib/session";34const paycux = new Paycux(process.env.PAYCUX_API_KEY);56export async function POST(request: Request) {7 const { user } = await requireSession();8 const { newEmail, factorId, code } = await request.json();910 const challenge = await paycux.mfa.verifyChallenge({ factorId, code });1112 if (!challenge.valid) {13 return Response.json({ error: "step_up_required" }, { status: 401 });14 }1516 await paycux.userManagement.updateUser({ userId: user.id, email: newEmail });1718 return Response.json({ ok: true });19}
Verify
Test the enrolment path, then test the ways it goes wrong. Most MFA incidents are recovery problems, not verification problems.
- 1Enrol a factor with a real authenticator app and confirm the issuer and account name read correctly on the phone.
- 2Enter a wrong code and confirm the factor stays unactivated rather than half-created.
- 3Sign out and back in, and confirm the challenge is required at the point your policy says it should be.
- 4Trigger a step-up action and confirm it refuses without a fresh code, even though the session is already MFA-verified.
- 5Use a backup code, then use the same one again. The second attempt must fail.
Reference
Request and response shapes for each of these live in the API reference. Paths are relative to https://api.paycux.com.
| Method | Path | What it does |
|---|---|---|
| POST | /mfa/factors | Enrol a factor and return its QR code and secret. |
| POST | /mfa/factors/:id/activate | Activate a pending factor with a first valid code. |
| GET | /mfa/factors | List the factors a user has enrolled. |
| DELETE | /mfa/factors/:id | Remove a factor from a user. |
| POST | /mfa/challenges | Issue a challenge for an activated factor. |
| POST | /mfa/challenges/:id/verify | Verify a submitted code and mark the challenge passed. |
| POST | /mfa/backup_codes | Regenerate a user's backup codes, invalidating the old set. |