barycenter/book/src/authentication/2fa-admin-enforced.md

125 lines
4 KiB
Markdown
Raw Normal View History

# Admin-Enforced 2FA
Admin-enforced two-factor authentication allows an administrator to mandate that a specific user must always complete a second authentication factor (passkey verification) after entering their password. This is the strongest 2FA enforcement mode -- the user cannot bypass it regardless of what they are accessing.
## How It Works
1. An administrator sets the `requires_2fa` flag for a user via the [Admin GraphQL API](../admin/graphql-api.md).
2. The flag is stored in the `users` table as `requires_2fa = 1`.
3. On the user's next login, after successful password authentication, Barycenter checks the flag.
4. If `requires_2fa = 1`, a **partial session** is created with `mfa_verified = 0` and the user is redirected to `/login/2fa`.
5. The user completes passkey verification.
6. The session is upgraded to `mfa_verified = 1` with `acr = "aal2"`.
## Enabling 2FA for a User
Use the `setUser2faRequired` GraphQL mutation on the admin API (default port 9091):
```graphql
mutation {
setUser2faRequired(username: "alice", required: true) {
success
message
requires2fa
}
}
```
Example using `curl`:
```bash
curl -X POST http://localhost:9091/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "mutation { setUser2faRequired(username: \"alice\", required: true) { success message requires2fa } }"
}'
```
### Response
```json
{
"data": {
"setUser2faRequired": {
"success": true,
"message": "2FA requirement updated for user alice",
"requires2fa": true
}
}
}
```
## Disabling 2FA for a User
Pass `required: false` to remove the enforcement:
```graphql
mutation {
setUser2faRequired(username: "alice", required: false) {
success
message
requires2fa
}
}
```
This removes the mandatory 2FA requirement. The user will still be prompted for 2FA if a [context-based trigger](./2fa-context-based.md) applies to a specific authorization request.
## Checking a User's 2FA Status
Use the `user2faStatus` query to inspect whether a user has 2FA enabled and whether they have passkeys enrolled:
```graphql
query {
user2faStatus(username: "alice") {
username
requires2fa
passkeyEnrolled
passkeyCount
passkeyEnrolledAt
}
}
```
### Response
```json
{
"data": {
"user2faStatus": {
"username": "alice",
"requires2fa": true,
"passkeyEnrolled": true,
"passkeyCount": 2,
"passkeyEnrolledAt": "2026-01-15T10:30:00Z"
}
}
}
```
## Database Column
The enforcement flag is stored in the `users` table:
| Column | Type | Description |
|----------------|---------|---------------------------------------------|
| `requires_2fa` | Integer | `0` = not required (default), `1` = required|
## The Redirect to /login/2fa
When Barycenter detects that 2FA is required for a user who has just authenticated with a password, it:
1. Creates a partial session with `mfa_verified = 0`, `amr = ["pwd"]`, `acr = "aal1"`.
2. Preserves the pending authorization request parameters in the session.
3. Returns an HTTP redirect to `/login/2fa`.
The `/login/2fa` page presents the user with a passkey verification prompt. Upon successful verification, the session is upgraded and the user is redirected back to `/authorize` to continue the OAuth flow.
See [2FA Flow Walkthrough](./2fa-flow.md) for the complete sequence.
## Considerations
- **Passkey enrollment**: A user must have at least one registered passkey before admin-enforced 2FA can be completed. If the user has no passkeys, they will be unable to satisfy the 2FA requirement and will be stuck at the `/login/2fa` page. Use the `user2faStatus` query to verify enrollment before enabling the flag.
- **Existing sessions**: Enabling `requires_2fa` does not invalidate existing sessions. The flag is checked at the next login. To force re-authentication, expire the user's current sessions.
- **Admin access**: The GraphQL admin API should be protected and not exposed publicly. See [Admin GraphQL API](../admin/graphql-api.md) for access control guidance.