Tenant Authentication

Start practitioner login

  • Sandbox: available
  • Staging: available
  • Production: unknown
POST
/auth/tenant/Practitioner/login/start

Starts a practitioner login: checks the email and password and returns either a session code plus the list of tenants the practitioner can enter, or, when the user has MFA enrolled, a login ID for the MFA step.

Auth: none. Scope: no tenant yet. A practitioner's User is server-wide; the tenant is chosen in login/token.

Behaviour

  • This is step 1 of a PKCE login. Generate a random codeVerifier, send its S256 hash as codeChallenge here, and send the codeVerifier itself to POST /auth/tenant/Practitioner/login/token.
  • Response nextStep:
    • token: no MFA. The response carries sessionCode and profiles, one entry per Practitioner membership of the user: its tenantCode, project (id, resourceType, name), profile, admin (true when the user is an admin of that project), isMainProject and parentProjectId.
    • mfa: the user has MFA enrolled. Pass loginId and the current TOTP code to POST /auth/tenant/Practitioner/login/mfa, which returns the same profiles list.
  • isMainProject is true when the project has no parent project, and null when the hierarchy could not be read. parentProjectId is set only when the caller is also a member of that parent; otherwise it is null.
  • PRACTITIONER_LOGIN_ENABLED is not checked here; it is checked in login/token against the chosen tenant.
  • A wrong email, an unknown account, an account without a password and a wrong password all give the same 401 Invalid email or password.
  • After 5 failed sign-ins for one email within 15 minutes, that email's sign-ins answer 429 with Retry-After for 15 minutes, the right password included, whether or not the email has an account. A successful sign-in resets the count.
  • Error responses are delayed to take at least one second.

Example

curl -X POST 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/start' \  -H 'Content-Type: application/json' \  -d '{    "email": "max.mustermann@example.com",    "password": "your-password",    "codeChallenge": "<base64url S256 hash of your codeVerifier>"  }'

Request Body

application/json
  1. body
email*string
Formatemail
password*string
codeChallenge*string

A randomly generated text that is sent from client application, the client application should generate it using the codeVerifier from the challange

Response Body

The next step: a session code for login/token, or a login ID for login/mfa.

application/json
  1. response
nextStep*"token"
Value in"token"
sessionCode*string
profiles*array<>
Example Request
curl -X POST 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/start' \  -H 'Content-Type: application/json' \  -d '{  "email": "user@example.com",  "password": "string",  "codeChallenge": "string"}'
Example Response
{  "nextStep": "token",  "sessionCode": "string",  "profiles": [    {      "project": null,      "profile": null,      "tenantCode": "string",      "admin": true,      "isMainProject": true,      "parentProjectId": "string"    }  ]}

Delete a sub tenant DELETE

Deletes a child project of your current project, with the setup Ovok created for it. **Auth:** bearer token of a project admin (`ProjectMembership.admin`). **Scope:** `:subProjectId` must be a direct child of the caller's project (from the token). ## Behaviour - A project that still holds a `Patient`, `Device` or `Location`, or is the parent of another project, is refused with 409 `project_not_empty`. The body names the count per type in `counts` and the child ids in `childProjectIds`. Move or delete those first. - Otherwise the route unlinks the project from its parent, deletes its memberships, Practitioners, AccessPolicies, PlanDefinitions, ClientApplications, Organizations and tenant code in one atomic write, deletes the `Project`, revokes its Signals tenant and finally deletes the parent-child relationship. Audit records (`Provenance`, `AuditEvent`) are kept. - Users are never deleted; they may belong to other projects. - Retry-safe: if the Signals revoke fails, the project is already deleted and the route answers 502 `signals_tenant_revoke_failed`. Call it again to finish. - A second delete of the same project while the first runs answers 409. ## Example ```bash curl -X DELETE 'https://api.sandbox.ovok.com/v1/internal/sub-project/c3499c27-2973-0a7f-807e-fb8676a92dcb' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```

Exchange practitioner session code for tokens POST

Finishes a practitioner login into one tenant: binds the session to the practitioner's membership in that tenant, then exchanges the session code and your PKCE `codeVerifier` for an access token and a refresh token. **Auth:** none. The session code and code verifier prove the earlier steps. **Scope:** the project that owns `tenantCode`. Pick it from the `profiles` returned by `login/start` or `login/mfa`. ## Behaviour - `PRACTITIONER_LOGIN_ENABLED` on the chosen project must not be `false` (it defaults to on). - The practitioner must have a membership in the chosen tenant. Ovok points the pending login at that membership before the code is redeemed, so the tokens are scoped to that project. - Ovok redeems the code with Medplum's `oauth2/token` endpoint (`grant_type=authorization_code`). - If the account was scheduled for deletion (`DELETE /auth/delete`), logging in cancels the deletion. - The response holds `accessToken`, `refreshToken`, `expiresIn` (seconds) and references to the `project` and `profile`. - Error responses are delayed to take at least one second. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/auth/tenant/Practitioner/login/token' \ -H 'Content-Type: application/json' \ -d '{ "sessionCode": "<sessionCode from login/start>", "codeVerifier": "<your codeVerifier>", "tenantCode": "my-medical-project" }' ```