Skip to content

Commit 34b0d33

Browse files
committed
Add MFA REST contract
Documents the TOTP-based Multi-Factor Authentication endpoints under /api/authn/mfa/, including: - Two-phase login flow with mfa_verified JWT claim - Setup, verify-setup, verify, disable, recovery-codes endpoints - Admin status and force-disable endpoints - Configuration properties Related to DSpace/DSpace#12978
1 parent 16bb43e commit 34b0d33

2 files changed

Lines changed: 236 additions & 0 deletions

File tree

endpoints.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* [/api/authn/login](authentication.md#Login)
2525
* [/api/authn/logout](authentication.md#Logout)
2626
* [/api/authn/status](authentication.md#Status)
27+
* [/api/authn/mfa](mfa.md)
2728
* [/api/captcha/challenge](captcha.md)
2829
* [/api/config/harvestermetadata](harvestermetadata.md)
2930
* [/api/config/submissiondefinitions](submissiondefinitions.md)

mfa.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Multi-Factor Authentication
2+
3+
[Back to the list of all defined endpoints](endpoints.md)
4+
5+
TOTP-based Multi-Factor Authentication (MFA) adds an optional second factor to user authentication. When enabled, a user must verify a time-based one-time password (TOTP) from an authenticator app after their initial login before gaining full API access.
6+
7+
## Configuration
8+
9+
Three configuration properties control MFA behavior:
10+
11+
| Property | Default | Description |
12+
|----------|---------|-------------|
13+
| `mfa.totp.enabled` | `true` | Global kill switch. When false, all MFA endpoints are unavailable and MFA verification is skipped. |
14+
| `mfa.totp.mandatory` | `false` | When true, all users MUST enroll in MFA. Unenrolled users receive restricted tokens until they complete setup. |
15+
| `mfa.totp.issuer` | `DSpace` | Display name shown in authenticator apps (e.g. Google Authenticator). |
16+
17+
## Two-Phase Login
18+
19+
When a user has MFA enabled, the login flow becomes two-phase:
20+
21+
1. **Phase 1**: Normal login via `POST /api/authn/login` succeeds but the JWT contains `mfa_verified: false`.
22+
2. **Phase 2**: The user calls `POST /api/authn/mfa/verify` with a valid TOTP code. A new JWT with `mfa_verified: true` is returned.
23+
24+
While `mfa_verified` is `false`, all endpoints return `403 Forbidden` with the following body:
25+
26+
```json
27+
{
28+
"error": "MFA verification required",
29+
"mfa_required": true
30+
}
31+
```
32+
33+
Exempt endpoints (accessible with `mfa_verified: false`):
34+
- `GET /api/authn/mfa/status`
35+
- `POST /api/authn/mfa/setup`
36+
- `POST /api/authn/mfa/verify-setup`
37+
- `POST /api/authn/mfa/verify`
38+
- `POST /api/authn/logout`
39+
- `GET /api/authn/status`
40+
41+
## JWT Claim
42+
43+
A new claim `mfa_verified` (boolean) is added to all JWTs:
44+
45+
| claim | description |
46+
|-------|-------------|
47+
| mfa_verified | `true` if MFA verification is complete or not required; `false` if the user must still verify |
48+
49+
## Status
50+
51+
**GET /api/authn/mfa/status**
52+
53+
Returns the current user's MFA enrollment status. Requires authentication.
54+
55+
```json
56+
{
57+
"enabled": false,
58+
"globallyEnabled": true,
59+
"mandatory": false,
60+
"setupRequired": false,
61+
"remainingRecoveryCodes": 0
62+
}
63+
```
64+
65+
Fields:
66+
- `enabled`: whether the user has MFA enrolled and active
67+
- `globallyEnabled`: whether MFA is enabled system-wide
68+
- `mandatory`: whether MFA enrollment is required for all users
69+
- `setupRequired`: true when MFA is mandatory but the user has not enrolled
70+
- `remainingRecoveryCodes`: number of unused recovery codes remaining
71+
72+
Return codes:
73+
- 200 OK
74+
- 401 Unauthorized if not authenticated
75+
76+
## Setup
77+
78+
**POST /api/authn/mfa/setup**
79+
80+
Initiates MFA enrollment by generating a new TOTP secret. Requires authentication. Can be called multiple times before verification (replaces the pending secret each time).
81+
82+
```json
83+
{
84+
"secret": "JBSWY3DPEHPK3PXP...",
85+
"provisioningUri": "otpauth://totp/DSpace:user@example.com?secret=JBSWY3DPEHPK3PXP...&issuer=DSpace&algorithm=SHA1&digits=6&period=30"
86+
}
87+
```
88+
89+
The `provisioningUri` is a standard `otpauth://` URI suitable for QR code generation.
90+
91+
Return codes:
92+
- 200 OK
93+
- 401 Unauthorized if not authenticated
94+
- 409 Conflict if MFA is already enabled for this user
95+
96+
## Verify Setup
97+
98+
**POST /api/authn/mfa/verify-setup**
99+
100+
Confirms MFA enrollment by verifying the first TOTP code from the user's authenticator app. This enables MFA and generates recovery codes.
101+
102+
Request body (JSON):
103+
```json
104+
{
105+
"code": "123456"
106+
}
107+
```
108+
109+
Response (200 OK):
110+
```json
111+
{
112+
"recoveryCodes": [
113+
"a1b2c3d4",
114+
"e5f6g7h8",
115+
"i9j0k1l2",
116+
"m3n4o5p6",
117+
"q7r8s9t0",
118+
"u1v2w3x4",
119+
"y5z6a7b8",
120+
"c9d0e1f2"
121+
]
122+
}
123+
```
124+
125+
Recovery codes are shown once and cannot be retrieved again. Each code is single-use.
126+
127+
Return codes:
128+
- 200 OK with recovery codes
129+
- 400 Bad Request if code is invalid
130+
- 401 Unauthorized if not authenticated
131+
- 404 Not Found if setup was not initiated
132+
133+
## Verify (Login)
134+
135+
**POST /api/authn/mfa/verify**
136+
137+
Verifies a TOTP code or recovery code during the two-phase login flow. On success, a new JWT with `mfa_verified: true` is returned in the `Authorization` response header.
138+
139+
Request body (JSON):
140+
```json
141+
{
142+
"code": "123456"
143+
}
144+
```
145+
146+
A recovery code can be used in place of a TOTP code. Recovery codes are case-insensitive and single-use.
147+
148+
Return codes:
149+
- 200 OK. New JWT in `Authorization` header.
150+
- 400 Bad Request if code is invalid or expired
151+
- 401 Unauthorized if not authenticated
152+
153+
## Disable
154+
155+
**POST /api/authn/mfa/disable**
156+
157+
Disables MFA for the current user. Requires a valid TOTP code for confirmation.
158+
159+
Request body (JSON):
160+
```json
161+
{
162+
"code": "123456"
163+
}
164+
```
165+
166+
Return codes:
167+
- 204 No Content on success
168+
- 400 Bad Request if code is invalid
169+
- 401 Unauthorized if not authenticated
170+
- 404 Not Found if MFA is not enabled
171+
172+
## Recovery Codes
173+
174+
**POST /api/authn/mfa/recovery-codes**
175+
176+
Regenerates recovery codes for the current user. Requires a valid TOTP code for confirmation. Previous codes are invalidated.
177+
178+
Request body (JSON):
179+
```json
180+
{
181+
"code": "123456"
182+
}
183+
```
184+
185+
Response (200 OK):
186+
```json
187+
{
188+
"recoveryCodes": [
189+
"a1b2c3d4",
190+
"e5f6g7h8",
191+
"i9j0k1l2",
192+
"m3n4o5p6",
193+
"q7r8s9t0",
194+
"u1v2w3x4",
195+
"y5z6a7b8",
196+
"c9d0e1f2"
197+
]
198+
}
199+
```
200+
201+
Return codes:
202+
- 200 OK with new recovery codes
203+
- 400 Bad Request if TOTP code is invalid
204+
- 401 Unauthorized if not authenticated
205+
- 404 Not Found if MFA is not enabled
206+
207+
## Admin Endpoints
208+
209+
### Admin Status
210+
211+
**GET /api/authn/mfa/admin/{uuid}/status**
212+
213+
Returns MFA status for any user. Requires admin privileges.
214+
215+
Response is the same as the regular status endpoint.
216+
217+
Return codes:
218+
- 200 OK
219+
- 401 Unauthorized if not authenticated
220+
- 403 Forbidden if not an admin
221+
- 404 Not Found if user does not exist
222+
223+
### Admin Disable
224+
225+
**POST /api/authn/mfa/admin/{uuid}/disable**
226+
227+
Force-disables MFA for any user without requiring a TOTP code. Invalidates all existing sessions for that user. Requires admin privileges.
228+
229+
No request body required.
230+
231+
Return codes:
232+
- 204 No Content on success
233+
- 401 Unauthorized if not authenticated
234+
- 403 Forbidden if not an admin
235+
- 404 Not Found if user does not exist or MFA is not enabled

0 commit comments

Comments
 (0)