Skip to main content
POST
Sign in with Apple (native)

Body

application/json
identityToken
string
required

The Apple identity token as delivered by ASAuthorizationAppleIDCredential.identityToken (a UTF-8 string, not the raw bytes) — a short-lived RS256 JWT signed by Apple. Verified against Apple's public JWKS (signature, issuer, audience allowlist, expiry); the raw Apple private key is never involved. Single-use in spirit (10-minute validity) — do not cache it client-side.

Example:

"eyJraWQiOiJXNldjT0tCIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwic3ViIjoiMDAxMjM0LmFiY2RlZi4xMjM0IiwiYXVkIjoiY29tLnByYXhpcy5wcmlhIiwiZXhwIjoxNzU3NTAwMDAwLCJpYXQiOjE3NTc0OTk0MDAsIm5vbmNlIjoiYWJjMTIzIiwiZW1haWwiOiJqYW5lLmRvZUBwcml2YXRlcmVsYXkuYXBwbGVpZC5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJpc19wcml2YXRlX2VtYWlsIjoidHJ1ZSJ9.signature"

nonce
string
required

Required (not optional — a replay control that can be skipped is not one). The RAW nonce the app generated for this sign-in attempt; the app sends Apple a SHA-256 digest of this same value. The server accepts the raw nonce or its SHA-256 as hex, base64 or base64url, matched against the nonce claim inside identityToken.

Example:

"f3a1c9e2b6d4"

fullName
object | null

Apple sends this ONLY on the very first authorization for a given Apple ID + app pair — send it whenever the native SDK callback provides it, or the account is created nameless (Apple will not resend it later without the user removing and re-granting the app). Ignored entirely when an existing user is matched by appleSub or by a verified email.

brandingId
string

Same optional branding hint accepted by /api/auth/autosignup and /api/auth/signin — switches the active institution to the branded twin when the user is already a member.

Example:

"pub_6430736fd62d650040420674"

Response

Signed in (or newly created and signed in). Same union as /api/auth/signin: either { token, profile }, or { mfaRequired: true, challengeId, factor, availableFactors, maskedEmail } when the resolved user has email MFA enabled — discriminate on mfaRequired === true and complete via POST /api/auth/mfa-verify.

Successful signin response shape. Two variants are returned by the same endpoint depending on whether email MFA is required: • JWT issued — { token, profile }. The user is signed in. • MFA challenge — { mfaRequired: true, challengeId, maskedEmail, codeSent?, stepUp? }. The client must POST the 6-digit code to /api/auth/mfa-verify with the challengeId; the verify endpoint then issues the JWT. Discriminate via mfaRequired === true (per Phase 1 design §6.1).

token
string

Signed JWT token. Present when MFA is not required or has just been verified. Include this in subsequent API requests via the x-access-token header or Authorization Bearer header. Expires after 6 hours (configurable via JWT_VALIDITY_SEC). Automatically refreshed on profile load (sliding session).

Example:

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2NDMwNzM2ZmQ2MmQ2NTAwNDA0MjA2NzQiLCJlbWFpbCI6ImpvaG4uZG9lQG15ZG9tYWluLmNvbSIsImN1c3RvbWVySWQiOiJjdXNfTnh4eHh4eCIsImFjY291bnRUeXBlIjoidXNlciIsInNlc3Npb25JZCI6InMlM0FhYmMxMjMiLCJpYXQiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDA4NjQwMH0.signature"

profile
object
brandingSwitch
object

Outcome of a branded-link switch attempt. Present only when the request carried a brandingId (an institution publicId from a /login?brandingId= link). The hint never grants access: switched requires an active membership on the twin (or platform-operator status) and an active parent account. Clients clear the stored hint on switched/already and show the no-access notice on refused.

mfaRequired
boolean

When true, the response is an MFA challenge — no JWT issued. Client should redirect to the MFA verify screen with the challengeId.

Example:

true

challengeId
string

MongoDB ObjectId of the issued mfaChallenge. Only present when mfaRequired: true. POST this to /api/auth/mfa-verify alongside the 6-digit code.

Example:

"6856fa89cbafcff8d98680f5"

maskedEmail
string

Partially-masked email address the verification code was sent to (for the verify-screen "code sent to …" prompt). Only present when mfaRequired: true.

Example:

"j*****e@example.com"

codeSent
boolean

false when the challenge comes from an SDK / Canvas launch (/api/auth/autosignup with institution_id or lticontextid): no code has been emailed yet. Show a "Send me a code" action that calls /api/auth/mfa-resend (the first send is not subject to the 30-second wait), then the code entry. A repeated launch of the same Digital Twin and course from the same network within the challenge's lifetime answers with the same challengeId, so a code already emailed keeps working; a launch of another Digital Twin or course, or from another network, gets its own challenge (a network keeps at most three pending launch challenges of a person — a fourth replaces the oldest). Absent or true means the code was emailed.

Example:

false

stepUp
boolean

true when the sign-in asks for a code before any session is opened — an embedded / Canvas launch, or a provider sign-in, of an account that needs a second proof first. Comes with codeSent: false: show "Send me a code" (/api/auth/mfa-resend), then the code entry; /api/auth/mfa-verify then signs the person in — on a launch, to the launched Digital Twin and course. Absent or false otherwise.

Example:

true