Web SDK
Abrova Auth for the browser (@abrova/auth-web). Anonymous, Apple, Google, e-mail and password, magic link and Telegram sign-in with only your public key; access token in memory, optional persistence, and a token provider for every Abrova SDK.
Uses the public API key only. Safe in a browser bundle
Plain TypeScript: React, Vue, Svelte, Next.js client code, or none
Nothing is stored unless you opt in
tokenProvider and getToken() for the other Abrova SDKs
createNonce() for Apple JS and Google Identity Services
linkApple, linkGoogle, linkPassword keep the user id
This page describes @abrova/auth-web 0.1.1.
A browser with fetch and crypto.getRandomValues (every current browser). Runs in client code only; Abrova Auth has no server SDK.
Using an AI assistant? Give it this page with Copy page for AI, or all the docs as plain text from /docs/llms.txt. See AI assistants.
Installation#
The package comes from Abrova Center. First point the @abrova scope at the Abrova registry in your project's .npmrc:
@abrova:registry=https://center.abrova.ir/api/packages/abrova/npm/
Then install:
npm install @abrova/auth-web
Or in package.json:
{
"dependencies": {
"@abrova/auth-web": "^0.1.1"
}
}In Abrova Console, turn on Abrova Auth for your project (Settings › Products) and the sign-in methods you want (Abrova Auth › Sign-in methods).
Initialization#
Create one client for your app, with your public API key (ab_live_...):
import { createAbrovaAuth } from '@abrova/auth-web';
export const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key' });
auth.onAuthStateChanged((userId) => {
console.log(userId ? `signed in as ${userId}` : 'signed out');
});| Option | Default | Description |
|---|---|---|
| apiKey | - | Your public API key (ab_live_...). Never the server secret. |
| storage | memory | Where the refresh token is kept between page loads. See Staying signed in. |
| storageKey | derived from the API key | Storage key, so two projects never share a session. |
| timeoutMs | 15000 | Request timeout in milliseconds. |
await auth.ready() resolves once a stored session (if any) is loaded. onAuthStateChanged calls your listener with the current user id at that point, and again on every sign-in or sign-out. It returns a function that stops listening.
Sign in#
const { methods } = await auth.getMethods(); // e.g. ['anonymous', 'google', 'password']
await auth.ready();
if (!auth.isSignedIn && methods.includes('anonymous')) {
await auth.signInAnonymously();
}
console.log(auth.currentUserId); // ru_...Show only the buttons for the methods getMethods() returns.
Use the user with other Abrova SDKs#
setUserId(auth.currentUserId)).import { AbrovaSync } from '@abrova/sync-web';
// Sync, Push and A/B Testing: null while signed out
const sync = new AbrovaSync({ apiKey: 'ab_live_your_api_key', tokenProvider: () => auth.getToken() });auth.getToken() resolves to a fresh access token, or null when nobody is signed in. auth.tokenProvider resolves to the token and rejects with UNAUTHENTICATED when nobody is signed in: use it for Abrova Chat, whose provider must return a token. After a sign-in or sign-out, call the other SDK's refresh method (for example sync.refreshUserToken()). More: Use with other Abrova products.
Sign-in methods#
Each method has its own page with the Console setup: Anonymous, Apple, Google, E-mail & password, Magic link, Telegram.
E-mail and password
const result = await auth.signUpWithEmail({ email, password, redirectUrl: 'https://example.com/auth/verify' });
// result.signedIn is false when the project requires a verified e-mail first.
await auth.signInWithEmail({ email, password });
await auth.sendPasswordReset({ email, redirectUrl: 'https://example.com/auth/reset' });
await auth.resetPassword({ token, password }); // token from the link's ?token=
await auth.verifyEmail({ token });
await auth.sendEmailVerification({ redirectUrl: 'https://example.com/auth/verify' });Magic link
await auth.sendMagicLink({ email, redirectUrl: 'https://example.com/auth/magic' });
// On the page the link opens:
const token = new URL(location.href).searchParams.get('token');
if (token) await auth.signInWithMagicLink({ token });Telegram
const { sent, botUrl } = await auth.requestTelegramCode({ phone: '+989121234567' });
if (!sent && botUrl) window.open(botUrl); // the user starts the bot and shares the number
await auth.verifyTelegramCode({ phone: '+989121234567', code: '123456' });Apple and Google
Run the provider's own sign-in with a nonce from the SDK, then hand its ID token to Abrova:
const { rawNonce, hashedNonce } = await auth.createNonce();
// Give hashedNonce to Google Identity Services or Sign in with Apple JS as the `nonce`.
await auth.signInWithGoogle({ idToken, rawNonce });
await auth.signInWithApple({ idToken, rawNonce, authorizationCode, name });Create a new nonce for every sign-in. Google sign-in needs a connection to Google, which some users in Iran don’t have. Anonymous, e-mail and password, magic link and Telegram code work without Google, so offer at least one of them as well. See Sign in with Google. On the web, Apple uses your Services ID: add it under Abrova Auth › Sign-in methods › Apple.
Upgrade an anonymous user#
Linking keeps the user id, so the user's data stays theirs:
import { isAbrovaAuthError } from '@abrova/auth-web';
try {
await auth.linkGoogle({ idToken, rawNonce }); // or linkApple(...), linkPassword({ email, password })
} catch (e) {
if (isAbrovaAuthError(e) && e.code === 'PROVIDER_ALREADY_LINKED') {
// This Google account already belongs to another user.
}
}The current user#
auth.currentUserId; // null when signed out
auth.isSignedIn;
const me = await auth.getMe(); // { id, anonymous, emailVerified, createdAt, providers }
auth.currentUser; // the user last loaded by getMe()Sign out and delete the account#
await auth.signOut(); // this device await auth.signOutEverywhere(); // every device await auth.deleteAccount(); // deletes the user, then signs out
Staying signed in#
By default nothing is stored: the user signs in again after a page reload. To keep the session, opt in to localStorage:
import { createAbrovaAuth, localStorageStorage } from '@abrova/auth-web';
const auth = createAbrovaAuth({ apiKey: 'ab_live_your_api_key', storage: localStorageStorage() });Any script on your page can read localStorage. An XSS bug or a compromised dependency could take the refresh token and with it the user's session. Use this only with a strict Content Security Policy and no untrusted scripts. The SDK never sets cookies.
You can also pass your own storage: an object with async get, set and remove.
Handle errors#
Every failure is an AbrovaAuthError with a code:
import { isAbrovaAuthError } from '@abrova/auth-web';
try {
await auth.signInWithEmail({ email, password });
} catch (e) {
if (isAbrovaAuthError(e) && e.code === 'INVALID_CREDENTIALS') showError('Wrong e-mail or password');
if (isAbrovaAuthError(e) && e.code === 'RATE_LIMITED') retryIn(e.retryAfterSec);
}Besides the server's codes the SDK uses NETWORK_ERROR. A network error never signs the user out; a revoked or expired session does (onAuthStateChanged(null)). The access token is renewed 2 minutes before it expires, and calls at the same time share one renewal. All codes: Error codes.
Troubleshooting#
Signed out after every reload
That is the default: the session is kept in memory. Pass storage: localStorageStorage() if your page has a strict Content Security Policy, or sign in anonymously again.
currentUserId is null right after a reload
The stored session loads asynchronously. Wait for auth.ready() or the first onAuthStateChanged call.
E-mail link refused with INVALID_INPUT
The redirectUrl must start with one of the link addresses under Abrova Auth › Settings.