Quickstart
React Native and Expo
Overview · React Native and Expo · Android · iOS · Register an app · API reference · Errors and security · Changelog
Coming soon @seletech/seleid-client is not on npm yet. These pages describe version 0.1.0 so you can plan an integration. See the roadmap.
Runs on React Native with a global fetch. Local token verification also needs crypto.subtle or a CryptoPort you pass in (Planned on the server side, see the overview). The SDK imports no Expo module; you pass storage in.
1. Install
npx expo install expo-secure-store expo-crypto expo-web-browser
npm install @seletech/seleid-client # planned, not published yet
2. Create the client
seleid.ts
import {
SeleIdClient,
TokenManager,
SecureStoreTokenStore,
SessionGuard,
} from '@seletech/seleid-client';
import * as SecureStore from 'expo-secure-store';
const CLIENT_ID = 'your-client-id'; // public; mobile apps are public clients and have no secret
export const seleid = new SeleIdClient({
// baseUrl defaults to https://auth.seleid.dev
// timeoutMs: 4000, retries: 2, budgetMs: 10000 (all optional)
onEvent: (e) => console.debug('seleid', e.type, e.op, e.durationMs, e.errorKind), // no tokens or personal data in events
});
export const tokens = new TokenManager({
client: seleid,
clientId: CLIENT_ID,
store: new SecureStoreTokenStore(SecureStore), // key seleid.tokens.v1
});
export const verifier = seleid.createVerifier({ audience: CLIENT_ID });
export const guard = new SessionGuard({ client: seleid, tokens, verifier });
3. Sign in (authorization code with PKCE)
SDK 0.1.0 has no authorize-URL or PKCE helper (Planned). The helper below is app code built on expo-crypto and expo-web-browser. SeleID requires client_id, an exact registered redirect_uri, response_type=code, a scope that includes openid (allowed: openid profile email), state, a nonce of 16 to 256 characters, and an S256 code_challenge of 43 to 128 characters.
signin.ts
import * as Crypto from 'expo-crypto';
import * as WebBrowser from 'expo-web-browser';
import { seleid, tokens, CLIENT_ID } from './seleid';
const REDIRECT_URI = 'com.example.app:/seleid'; // must match a registered redirect URI exactly
function b64url(base64: string): string {
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
async function randomString(bytes = 32): Promise<string> {
const raw = Crypto.getRandomBytes(bytes);
let bin = '';
raw.forEach((b) => (bin += String.fromCharCode(b)));
return b64url(btoa(bin)); // 43 characters for 32 bytes
}
export async function signIn() {
const { authorization_endpoint } = await seleid.discovery();
const verifier = await randomString(32);
const state = await randomString(16);
const nonce = await randomString(16);
const challenge = b64url(
await Crypto.digestStringAsync(Crypto.CryptoDigestAlgorithm.SHA256, verifier, {
encoding: Crypto.CryptoEncoding.BASE64,
})
);
const url =
authorization_endpoint +
'?' +
new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: 'openid profile email',
state,
nonce,
code_challenge: challenge,
code_challenge_method: 'S256',
}).toString();
const result = await WebBrowser.openAuthSessionAsync(url, REDIRECT_URI);
if (result.type !== 'success') return null; // cancelled
const params = new URL(result.url).searchParams;
if (params.get('state') !== state) throw new Error('state mismatch');
const code = params.get('code');
if (!code) throw new Error(params.get('error') ?? 'no_code');
const tokenSet = await seleid.exchangeCode({
clientId: CLIENT_ID,
code,
redirectUri: REDIRECT_URI,
codeVerifier: verifier,
});
await tokens.setTokens(tokenSet); // { accessToken, refreshToken, idToken, scope, expiresAt }
return tokenSet;
}
4. Check the session
import { isSeleIdError } from '@seletech/seleid-client';
import { guard } from './seleid';
// Browsing, feeds, playback: local verification only, no SeleID call.
await guard.check();
// Sensitive actions (payments, password change): online check, fails closed.
try {
const { context, source } = await guard.check({ sensitive: true });
// context.status === 'active'; source is 'online' or 'cached' (cache lives 30 s)
} catch (error) {
if (!isSeleIdError(error)) throw error;
if (error.isUnknown) {
showRetry('We could not confirm your account. Try again.'); // timeout, network, unavailable
} else if (error.endsSession) {
goToSignIn(); // revoked, disabled, deleted: tokens are already cleared
}
}
Reminder: until SeleID deploys /v1/session/context and JWT access tokens, these checks fail with a typed error in production. Build your UI for that case now.
5. Get an access token for your own API
const accessToken = await tokens.getAccessToken(); // refreshes first if less than 30 s are left
// A timeout or outage keeps the stored tokens; only a definitive refusal clears them.
6. Sign out
await tokens.clear(); // local sign-out. Server-side token revocation and end-session endpoints are Planned.
7. Test without a network
import { FakeSeleId } from '@seletech/seleid-client/testing';
import { SeleIdClient } from '@seletech/seleid-client';
const fake = await FakeSeleId.create(); // real RS256 keys
const client = new SeleIdClient({ fetch: fake.fetch });
const token = await fake.issueAccessToken(); // valid access token for the fake subject
fake.bumpGeneration(); // simulate "sign out everywhere"
fake.setStatus('disabled'); // simulate a disabled account
fake.failNext('/v1/session/context' /* , ...failures */);
A hosted sandbox with test accounts is Planned; FakeSeleId is the supported way to test today. A sample project repository is Planned.