Skip to content

Authentication factors cannot be seeded, so MFA administration needs post-boot API calls #75

Description

@danielloader

WorkOSSeedConfig has eleven keys (organizations, users, connections, pipeConnections, invitations, roles, permissions, webhookEndpoints, connectApplications, apiKeys, jwtTemplate) and none of them covers authentication factors. There are no hits for totp anywhere in the seed path, so the only way to get a factor is POST /user_management/users/{id}/auth_factors or the legacy /auth/factors/enroll after the emulator is up.

That makes anything touching MFA administration a two-step setup: seed the users, then walk them over HTTP enrolling factors, because ListAuthFactors returning an empty list is indistinguishable from "this user has no MFA". With state in memory, the enrolment also does not survive a restart while the seeded users do, so the environment comes back subtly different from how it was set up.

A seeded user can already be given every other authentication method, which is what makes the omission look accidental rather than deliberate:

  • password writes a password_hash, so the password grant works
  • oauth_provider calls linkOAuthIdentity, so GET /user_management/users/{id}/identities reports it

A per-user flag would sit alongside those:

users:
  - email: alice@example.com
    password: test123
    totp: true          # or mfa: [totp]

inserting into the same ws.authFactors collection the two existing routes write to, with the shape routes/auth-factors.ts already builds (object: 'authentication_factor', type: 'totp', totp: { issuer, user, uri }), defaulting issuer the way that route does.

One optional addition while the area is open: the TOTP secret is randomBytes(20) per enrolment and surfaces only inside the returned uri, so nothing can compute a valid code for a seeded factor deterministically. /auth/factors/:id/challenge and /auth/challenges/:id/verify are both modelled, so accepting a pinned totp_secret in the seed would make the whole challenge-and-verify path testable rather than just the existence of a factor. Reasonable to leave out if that is out of scope.

Is there a reason factors are deliberately API-only, or would a seed key be accepted?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions