Signing in
Every sign-in method, two-factor, OAuth, bot checks, organizations, the user's own account and signing out, with @authoric/js.
Every way in answers the same way: a session, or a second factor still owed. The result is typed so you handle both.
const result = await authoric.auth.signInWithPassword({ email, password });
if (result.mfa) {
// result.mfa.factors says which factors the user has; result.mfa.activeKind which one was asked
const done = await authoric.auth.mfa.verify({
challengeId: result.mfa.challengeId,
code,
rememberDevice: true, // skip the second factor on this device next time
});
// done.session, or done.mfa again for a wrong code
} else {
console.log(result.session.user.email, result.session.activeOrgUid);
}Which methods your app offers, for rendering the sign-in page before anyone signs in:
const methods = await authoric.auth.methods();
// { password, magicLink, emailOtp, oauth, signup, botCheck }The methods
| Method | Call |
|---|---|
| Password | auth.signUp({ email, password, fname?, lname?, verifyRedirectTo? }), auth.signInWithPassword({ email, password }) |
| Password reset | auth.requestPasswordReset({ email, redirectTo? }), then auth.confirmPasswordReset({ token, newPassword }) |
| Magic link | auth.magicLink.request({ email, redirectTo? }), then on the link's page auth.magicLink.verify({ token }) |
| Email code | const { requestId } = await auth.otp.request({ email }), then auth.otp.verify({ requestId, code }) |
| OAuth | auth.oauth.redirect(provider, { redirectTo }), then on that page auth.oauth.handleCallback() |
Requests that send an email always answer the same way, whether or not the address has an account.
redirectTo must be on your app's allowed domains, or
localhost.
Signing up
A password sign-up is sent a "confirm your email" link. verifyRedirectTo is the page of yours it
opens, and that page confirms the address with the token the link carries:
const result = await authoric.auth.signUp({
email,
password,
verifyRedirectTo: "https://app.example.com/confirm-email",
});
if (result.verificationRequired) {
showCheckYourEmail(result.email); // no session yet: the app requires a confirmed address
} else {
console.log(result.session.user.email); // signed in
}
// On https://app.example.com/confirm-email:
const token = new URL(location.href).searchParams.get("token");
await authoric.auth.verifyEmail.confirm({ token }); // works signed outsignUp answers { session }, or { verificationRequired: true, email } when your app requires a
confirmed address before anyone signs in; then a password sign-in throws EMAIL_NOT_VERIFIED until
the link is followed. Otherwise the user is signed in at once, and auth.verifyEmail.request()
sends the link again. Until the address is confirmed, the user cannot send invitations or create
organizations (EMAIL_NOT_VERIFIED). auth.getUser() says whether it is (user.emailVerified).
OAuth
// On your sign-in page:
await authoric.auth.oauth.redirect("google", { redirectTo: "https://app.example.com/auth/callback" });
// On https://app.example.com/auth/callback:
const { result, redirectTo } = await authoric.auth.oauth.handleCallback();handleCallback reads the result from the address, exchanges its single-use code for a session, and
removes the code from the address bar and history. A failed sign-in throws an AuthoricError whose
code says why (for example OAUTH_EMAIL_UNVERIFIED).
Bot checks
When your app's bot check is on, auth.methods() returns its Turnstile site
key. Render the widget with it, and pass its token to the guarded calls (sign-up, sign-in, password
reset, magic link and email code requests):
await authoric.auth.signUp({ email, password, botCheckToken: turnstileToken });Two-factor
For the signed-in user:
await authoric.auth.mfa.list(); // their factors
// An authenticator app: show `keyUri` as a QR code (and `secret` for typing in), then confirm a code.
const { secret, keyUri } = await authoric.auth.mfa.enroll({ kind: "totp" });
await authoric.auth.mfa.confirm({ kind: "totp", code });
// A phone: a code is texted to it, then confirmed with the enrollment it belongs to.
const { enrollmentId } = await authoric.auth.mfa.enroll({ kind: "sms", phone: "+15555550123" });
await authoric.auth.mfa.confirm({ kind: "sms", code, enrollmentId });
await authoric.auth.mfa.regenerateRecoveryCodes(); // show them once
await authoric.auth.mfa.forgetDevices(); // remembered devices ask againWhen an organization requires two-factor and the user has none, the
session says mfaEnrollmentRequired: true and reaches only enrollment. After they add a factor, call
auth.refresh() for a full session.
Organizations
A session acts in one organization at a time, session.activeOrgUid. Every org-scoped read and write
is in that organization.
const session = await authoric.auth.switchOrg(orgUid);Members, invitations and roles: Teams and roles.
The user's own account
const me = await authoric.auth.getUser(); // asks the API: { user, activeOrgUid, org }
await authoric.auth.updateProfile({ fname: "Dana", lname: "Reyes" });
await authoric.auth.changePassword({ currentPassword, newPassword }); // other devices are signed outchangePassword signs out every other device and emails the user that the password changed. An
account with no password (one that signs in with Google, GitHub or a sign-in link) gets
PASSWORD_NOT_SET: send it the password reset email instead.
Proving it is still you
Changing the email address and deleting the account each need a fresh proof first. reverify
takes exactly one of a password, a two-factor code, or a code emailed to the user, and answers a
receipt that works once, for ten minutes, from this session only:
const { reverifyToken } = await authoric.auth.reverify({ password });
// or: await authoric.auth.reverify({ mfaCode, mfaKind: "totp" })
// or: const { requestId } = await authoric.auth.reverify.emailCode();
// await authoric.auth.reverify({ requestId, code }) // the 6-digit code from the emailA failed proof throws REVERIFY_REQUIRED, and its details.methods lists the proofs this account
can give (password, totp, sms, email), so you can show the right prompt.
Changing the email address
await authoric.auth.changeEmail({
newEmail: "dana.reyes@example.com",
reverifyToken,
redirectTo: "https://app.example.com/confirm-new-email",
}); // { sent: true }
// On https://app.example.com/confirm-new-email (signed in or not):
const token = new URL(location.href).searchParams.get("token");
const { email } = await authoric.auth.changeEmail.confirm({ token });A link goes to the new address and a notice to the current one. The answer is the same whether or not the new address already has an account. Once the link is followed, the account moves, and every session except the one that asked ends.
Deleting the account
const { orgsDeleted } = await authoric.auth.deleteAccount({ reverifyToken }); // signs out here tooThe account's personal details are erased and every way in is removed. An organization the user was
the only member of is deleted; its rows are kept for you. The address can sign up again. It throws
ACCOUNT_LAST_OWNER (with details.orgs) while the user is the last owner of an organization with
other members, ACCOUNT_HAS_SUBSCRIPTION while a subscription is still billing, and
ACCOUNT_DELETE_DISABLED when your app does not let users delete their own account.
The REST routes, the app settings that shape them, and every refusal: The user's own account.
Sessions and signing out
await authoric.auth.sessions.list(); // the user's signed-in devices
await authoric.auth.sessions.revoke(sessionUid); // sign one out
await authoric.auth.sessions.revokeOthers(); // sign out everywhere else
await authoric.auth.signOut(); // this device, in every tabsignOut ends the session on the server and clears it here, in every tab, even when the server
cannot be reached.
auth.getAccessToken() returns a current access token for calling your own backend, refreshing it
first if it is about to expire. Your backend verifies it by calling Authoric with it.
On a server
For a server that renders pages, createServerClient keeps the session in a cookie on your own
domain: see Server-side sign-in.
A native app can keep the refresh token in its own secure store, through a storage adapter, or
hand one back to the client itself:
const authoric = createClient({ url, appUid, storage: "memory" });
await authoric.auth.setSession({ refreshToken, user }); // the next call mints an access token