Skip to main content

Patient sign-in

A patient signs in to the tenant they belong to. The flow has up to three steps: start, an optional multi-factor step, and a token exchange.

StepMethodPath
1POST/auth/tenant/Patient/login/start
2, only with MFAPOST/auth/tenant/Patient/login/mfa
3POST/auth/tenant/Patient/login/token

None of the three needs a bearer token.

Before you start​

You needWhere it comes from
The tenant codeFind your tenant code.
A patient account in that projectPatient registration or a patient invitation.
Patient sign-in switched onPATIENT_LOGIN_ENABLED must not be false. It is on when unset.

The account must be a Patient of that tenant's project. A practitioner with the same email cannot sign in here; use practitioner sign-in.

Step 0: Create a code verifier and challenge​

Create these in your app for every sign-in attempt, and keep the verifier until step 3. The verifier is a random string of 43 to 128 URL-safe characters; the challenge is its SHA-256 hash, base64url-encoded without padding.

const base64url = (bytes) =>
btoa(String.fromCharCode(...new Uint8Array(bytes)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(48)));
const codeChallenge = base64url(
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)),
);

The same in a shell:

VERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

Step 1: Start sign-in​

Checks the email and password for one tenant.

curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Patient/login/start' \
--header 'Content-Type: application/json' \
--data '{
"email": "patient@example.com",
"password": "your-password",
"tenantCode": "big-health-company",
"codeChallenge": "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
}'
Body fieldDescription
emailThe patient's email. Ovok lowercases it.
passwordThe patient's password.
tenantCodeThe tenant to sign in to.
codeChallengeThe hash from step 0.

The answer tells you what to do next:

Response fieldExampleMeaning
nextSteptoken or mfaTells the app which sign-in step to show next.
sessionCodeTemporary codePresent when nextStep is token; send it with the verifier in step 3.
loginIdUUIDPresent when nextStep is mfa; send it with the one-time code in step 2.
StatusMessageCause
404Tenant not found.The tenant code is wrong. Checked first.
403Login is not enabled.PATIENT_LOGIN_ENABLED is false.
400Username or password is incorrect.A wrong email, an unknown account, an account without a password, a wrong password, or an account that is not a patient of this tenant. All give the same answer on purpose.
422A list of path and messageA body field is missing or invalid.
429More than 10 requests in a minute.

Step 2: Verify the one-time code (only with MFA)​

curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Patient/login/mfa' \
--header 'Content-Type: application/json' \
--data '{"loginId":"8c1e5a52-6f0b-4d9e-9a41-0b2f7c3d9e10","mfaToken":"123456"}'
Body fieldDescription
loginIdThe loginId from step 1. A UUID.
mfaTokenThe current one-time code from the patient's authenticator app, at least 6 characters.

On success the answer is { "nextStep": "token", "sessionCode": "…" }. Use the sessionCode in step 3.

StatusMessageCause
400Invalid MFA token.The code is wrong or expired, or the loginId is unknown.
422A list of path and messageloginId is not a UUID, or mfaToken is shorter than 6 characters.
429More than 5 requests in a minute. Few guesses are allowed on purpose.

Step 3: Exchange the session code for tokens​

curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Patient/login/token' \
--header 'Content-Type: application/json' \
--data '{"sessionCode":"<sessionCode from step 1 or 2>","codeVerifier":"<your code verifier>"}'

This step takes no tenant code: the session code already belongs to the tenant you started in.

Response fieldExampleMeaning
accessTokenToken stringBearer token for API calls.
refreshTokenToken stringToken used to renew the session.
expiresIn3600Access-token lifetime in seconds.
project.reference, project.displayProject/<id>, Big Health CompanyProject the tokens are for.
profile.reference, profile.displayPatient/<id>, Example PatientPatient profile that signed in.
StatusMessageCause
400Code verification failed.The sessionCode or the codeVerifier is wrong.
400Invalid user profile.The session belongs to something other than a patient.
400Account not found.The patient account could not be found.
429More than 30 requests in a minute.

If the patient had scheduled their account for deletion, signing in cancels the deletion.

End to end in a shell​

This signs in a patient who has no multi-factor step. It needs jq.

VERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
CHALLENGE=$(printf %s "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')

SESSION=$(curl --silent --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Patient/login/start' \
--header 'Content-Type: application/json' \
--data "{\"email\":\"patient@example.com\",\"password\":\"${PASSWORD}\",\"tenantCode\":\"big-health-company\",\"codeChallenge\":\"${CHALLENGE}\"}" \
| jq -r '.sessionCode')

curl --request POST \
--url 'https://api.sandbox.ovok.com/auth/tenant/Patient/login/token' \
--header 'Content-Type: application/json' \
--data "{\"sessionCode\":\"${SESSION}\",\"codeVerifier\":\"${VERIFIER}\"}"

Gotchas​

  • The setting is checked at step 1 only. A session code issued before you turned PATIENT_LOGIN_ENABLED off can still be exchanged at step 3.
  • Turning the switch off does not end sessions. A patient who is already signed in keeps working until their token expires, up to an hour.
  • Registration returns tokens too. A patient who registers is signed in even if sign-in is off. Use PATIENT_REGISTRATION_ENABLED to stop registration.
  • Projects created with POST /v1/slim/project/child start with patient sign-in off. Turn it on before you launch.
  • A failed read of the project looks like "off". If the project cannot be read for a moment, callers see the 403.
  • Keep the verifier private. Do not send it anywhere before step 3, and do not reuse it across sign-in attempts.
  • Every error takes at least a second. Show a spinner, not an outage message.