Local login and MFA
Local login authenticates the user with HTTP Basic and establishes the session through a cookie. If the user has MFA enabled, the API asks for a second factor before it completes the login.
sequenceDiagram
participant C as Client
participant A as Riversoft STCP DirectLink API
C->>A: POST /auth/login (Authorization: Basic)
alt status 0: no MFA
A-->>C: 200 OK + Set-Cookie __Host-dirl_session
else status 1: PIN by email or SMS
A-->>C: 206 + linkURL (/auth/mfa)
C->>A: POST linkURL (p=PIN)
A-->>C: 200 OK + Set-Cookie
else status 2: TOTP registered
A-->>C: 206 + linkURL (/auth/mfa)
C->>A: POST linkURL (p=TOTP code)
A-->>C: 200 OK + Set-Cookie
else status 3: TOTP registration
A-->>C: 206 + linkURL (/auth/mfa/register) + secret + qrcode
Note over C: The user adds the account in the authenticator app
C->>A: POST linkURL (p=TOTP code)
A-->>C: 200 OK (registration done, no session)
C->>A: POST /auth/login (new login)
A-->>C: 206 status 2 (follow the registered TOTP path)
end
Log in
Send GET or POST to /auth/login with the credentials in HTTP Basic. The endpoint does not accept any other type of credential.
read -r -p 'User: ' API_USER
read -r -s -p 'Password: ' API_PASSWORD; printf '\n'
curl -i -c cookies.txt \
--user "$API_USER:$API_PASSWORD" \
-X POST \
"$API_BASE/auth/login"The -c cookies.txt option stores the session cookie for subsequent calls.
Tip
When the administrator enables authentication through a dedicated provider, enter the user in the PROVIDER\USER format.
Cloudflare Turnstile
When the administrator enables Cloudflare Turnstile validation, /auth/login, /auth/forget and /auth/firstaccess require the token generated by the Turnstile widget in the CF-Turnstile-Response header. The API validates the token with Cloudflare before checking the credentials.
read -r -s -p 'Turnstile token: ' TURNSTILE_TOKEN; printf '\n'
curl -i -c cookies.txt \
--user "$API_USER:$API_PASSWORD" \
-H "CF-Turnstile-Response: $TURNSTILE_TOKEN" \
-X POST \
"$API_BASE/auth/login"The token is single-use and expires in 300 seconds: generate a new one for each attempt. When validation is disabled, the header is ignored.
| HTTP | Situation |
|---|---|
400 | Header missing or longer than 2048 characters. |
403 | Token rejected: invalid, expired or already used. |
503 | Could not reach Cloudflare; try again. |
Tip
The Turnstile widget runs in the browser. Integrations without a user interface should obtain the Bearer token through the password grant or token exchange, which do not require the Turnstile token.
Response without MFA
When the login does not require MFA and the access checks pass, the API responds 200 and sends the secure __Host-dirl_session cookie:
HTTP/1.1 200 OK
Set-Cookie: __Host-dirl_session=<token>; Path=/; Secure; HttpOnly; SameSite=Strict
Content-Type: application/json
{
"status": 0,
"description": "<description returned by the API>",
"apiver": "v2 (9.0.1)",
"user": "<user>",
"urllink": "<portal link>",
"time": "<date and time>"
}The session token is delivered only in the cookie; it does not appear in the JSON. If your integration needs a Bearer token, use the password grant.
Complete MFA
If the user has MFA, the login responds 206 Partial Content. The status field indicates the step the frontend must present:
status | Situation | What to do | linkURL |
|---|---|---|---|
0 | Login complete, no MFA (200 response). | Continue with the session. | — |
1 | PIN sent by email or SMS. | Ask for the PIN received. | /auth/mfa |
2 | TOTP already registered. | Ask for the authenticator app code. | /auth/mfa |
3 | TOTP not registered yet. | Show the QR code for registration and ask for the first code. | /auth/mfa/register |
The method comes from the instance configuration (AuthMFAMethod: email, SMS or authenticator app). The user registration can set another method or disable MFA for that user only. MFA applies to local login with HTTP Basic.
Answer the challenge (status 1 and 2)
{
"status": 2,
"description": "handlerAuthLogin:require MFA",
"apiver": "v2 (9.0.1)",
"user": "<user>",
"linkURL": "<secure link to /auth/mfa>",
"secret": "",
"qrcode": "",
"time": "<date and time>"
}In status 1, the response has no secret and qrcode fields, and the API sends the PIN to the user’s email or mobile phone. In status 2, both fields are empty.
Send the PIN or TOTP code in the p form field to the full URL received in linkURL:
export MFA_URL='<linkURL received at login>'
read -r -s -p 'PIN or TOTP code: ' MFA_CODE; printf '\n'
curl -i -c cookies.txt -X POST \
--data-urlencode "p=$MFA_CODE" \
"$MFA_URL"With a valid code, the API completes the login: it responds 200 with the same body as the response without MFA and sends the session cookie.
Register TOTP (status 3)
When the method is the authenticator app and the user has no TOTP registered yet, the API generates a new secret and returns it in the 206 response:
{
"status": 3,
"description": "handlerAuthLogin:require MFA",
"apiver": "v2 (9.0.1)",
"user": "<user>",
"linkURL": "<secure link to /auth/mfa/register>",
"secret": "<TOTP secret in Base32>",
"qrcode": "<PNG image in URL-safe Base64>",
"time": "<date and time>"
}| Field | Use |
|---|---|
secret | TOTP secret, to type in the app when the QR code cannot be read. |
qrcode | 200 × 200 pixel PNG image with the secret’s QR code, in URL-safe Base64 (RFC 4648, with - and _). In the app, the account appears as STCPDirectLink (<instance>), with the user name. |
To show the QR code, convert it to standard Base64 before building the data: URL:
const b64 = response.qrcode.replace(/-/g, '+').replace(/_/g, '/');
image.src = `data:image/png;base64,${b64}`;Registration follows these steps:
Show the QR code and the secret and ask the user to add the account in the authenticator app (Microsoft Authenticator, Google Authenticator or another TOTP-compatible app).
Ask for the 6-digit code generated by the app and send it in the
pfield to thelinkURLreceived:export MFA_REGISTER_URL='<TOTP registration linkURL>' read -r -s -p 'TOTP code: ' MFA_CODE; printf '\n' curl -i -X POST \ --data-urlencode "p=$MFA_CODE" \ "$MFA_REGISTER_URL"With a valid code, the API stores the secret and responds
200:{ "status": 0, "description": "handlerAuthMfaRegister:succsessfully", "apiver": "v2 (9.0.1)", "user": "<user>", "length": 0, "time": "<date and time>" }Log in again at
/auth/login. The response is now206withstatus2: finish with the app code, as in Answer the challenge.
Important
Registration does not establish the session: the /auth/mfa/register response has no cookie. The login only finishes after the new /auth/login and the validation at /auth/mfa.
Errors and validity
| HTTP | Situation |
|---|---|
400 | Invalid or expired link, or missing p field. Start again at /auth/login. |
403 | Invalid TOTP code, user locked or not found. |
405 | Method other than GET and POST. |
The link is valid for the time set in secureLinkPinExpireSeconds, and the PIN sent by email or SMS has the length set in secureLinkPinMaxLen (see the Riversoft STCP DirectLink configuration).
Warning
MFA links are protected and expire. Do not edit them, do not reuse them and do not build the parameters manually: always use the full URL issued by the API. The TOTP secret is sensitive data: show it only to the user and do not write it to logs.