Log a user in.
Authenticates a user and returns a JWT access token (plus a refresh token).
Post a JSON body with the user’s credentials:
{ "username": "user@example.com", "password": "their-password" }
On success the response is 200 with an access token and refresh token:
{ "access_token": "eyJ...", "refresh_token": "...", "token_type": "Bearer", "expires_in": 31536000 }
Two-factor authentication. If the user has a verified 2FA method,
the password alone is not enough. After verifying the password this
endpoint instead returns 202 Accepted with a challenge that must be
satisfied via POST /api/v1/tokens/two-factor:
{
"two_factor_required": true,
"challenge_token": "eyJ...",
"mechanism": "Totp",
"masked_destination": "",
"expires_in": 600,
"available_mechanisms": ["Totp", "Email"],
"recovery_codes_available": true
}
mechanism is the method this challenge targets. When it is Email,
a one-time code has already been emailed to the user (see
masked_destination); when it is Totp, the user reads the current
code from their authenticator app and nothing is sent.
available_mechanisms lists every method the user has enrolled so a
client can offer a switch via POST /api/v1/tokens/two-factor/switch-mechanism.
If the tenant forces 2FA but the user has not enrolled yet, the 202
body instead contains "two_factor_enrollment_required": true with a
challenge_token to drive the mid-login enrollment endpoints.
Invalid credentials return 400 with a body of invalid_grant.