Skip to content

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.

HTTPSituation
400Header missing or longer than 2048 characters.
403Token rejected: invalid, expired or already used.
503Could 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:

statusSituationWhat to dolinkURL
0Login complete, no MFA (200 response).Continue with the session.—
1PIN sent by email or SMS.Ask for the PIN received./auth/mfa
2TOTP already registered.Ask for the authenticator app code./auth/mfa
3TOTP 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>"
}
FieldUse
secretTOTP secret, to type in the app when the QR code cannot be read.
qrcode200 × 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:

  1. 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).

  2. Ask for the 6-digit code generated by the app and send it in the p field to the linkURL received:

    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"
  3. 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>"
    }
  4. Log in again at /auth/login. The response is now 206 with status 2: 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

HTTPSituation
400Invalid or expired link, or missing p field. Start again at /auth/login.
403Invalid TOTP code, user locked or not found.
405Method 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.

Next steps