@antzsoft/wso2-auth-reactnative
v1.6.7
Published
WSO2 Identity Server App-Native Authentication for React Native — browserless, no WebView, full PKCE + auto token refresh
Readme
@antzsoft/wso2-auth-reactnative
WSO2 Identity Server App-Native Authentication for React Native — fully browserless, no WebView, pure REST. Supports both Expo (managed & bare) and React Native CLI (bare) projects.
What's new in v1.6.5
Network error resilience — no unintended logout on offline/timeout:
doRefreshno longer logs out on network failure:TypeError(no internet, DNS, ECONNREFUSED) andAbortError(timeout) are now distinguished from auth errors (4xxinvalid_grant). On network error the refresh returns silently; only genuine 4xx auth failures trigger logout.- 5xx server errors no longer cause logout: A WSO2 500 during token refresh retries on the next cycle. The refresh token is still valid in this case.
- App launch offline with expired access token:
restoreSession()previously returned'expired'(triggering logout) when the network was down and the access token had expired. It now returns the stored tokens as-is so the user stays authenticated; the proactive timer retries the refresh when connectivity returns. - Session poll network errors swallowed: If
getSessionInfo()fails due to network (not 401), the poll skips the refresh attempt and retries on the next interval tick. The innerdoRefreshcatch also checks for network errors before logging out.
What's new in v1.6.0 – v1.6.4
sessionPollIntervalSeconds — remote revocation detection:
- New opt-in config in
AuthProvider. When set (e.g.sessionPollIntervalSeconds: 180), the SDK pollsGET /api/users/v1/me/session-infoevery N seconds while the app is in the foreground. - The session-info endpoint uses DB-backed validation — returns 401 immediately when WSO2 revokes tokens (password changed on another device), even while the JWT signature is still valid.
- On 401 from poll: attempts
doRefresh; if that also fails with an auth error → clears storage + firesonSessionExpired. - Paused automatically via
AppStatewhen app is backgrounded; resumes on foreground. - Default:
0(disabled). Recommended:180(3 min).
What's new in v1.5.3
Daily expiry catch-up — reliability fixes (matches web SDK v1.3.6):
Catch-up no longer fires immediately after fresh login: The AppState catch-up check now skips if the user logged in after today's configured check time — meaning the token was freshly issued and cannot be expiring soon. Uses
antz_auth_login_time(epoch ms, written to storage at login). If login was before the check time (e.g. logged in at 8 AM, check time is 9:43 AM), the catch-up correctly fires when the app is killed and reopened after 9:43 AM.LAST_DAILY_CHECKsurvives logout:clearStorage()no longer removesantz_auth_last_daily_check— preserving it prevents the catch-up from re-firing after a logout+login cycle on the same day.
What's new in v1.5.0
onSessionExpired callback + Daily expiry check:
onSessionExpired— new callback inAuthProviderconfig. Fires when the SDK detects the refresh token is dead (proactive timer or foreground check).statusis already'unauthenticated'and tokens are cleared when this fires. Use for custom UX: alerts, analytics, navigation.RootNavigatorstill switches stacks automatically viastatus— this callback is additive.Daily expiry check — new opt-in feature (
enableDailyExpiryCheck: true). FiresonDailyExpiryWarningonce per day at a configurable local time (default 5 AM) when the refresh token will expire within a configurable window (default 24 h). The SDK does not auto-logout; your callback decides the UX. Handles all mobile cases: app open continuously (setTimeout), app backgrounded (AppState 'active' catch-up), app killed and reopened (lastCheckDatepersisted in secure storage).Refresh token expiry stored after login —
getSessionInfo()is called fire-and-forget after every login. The refresh token's absolute expiry is persisted in secure storage so the daily check reads it without a network call. Cleared onlogout().
New config fields: onSessionExpired, enableDailyExpiryCheck, dailyCheckHour, dailyCheckMinute, expiryWarningWindowHours, onDailyExpiryWarning.
What's new in v1.4.7+
getSessionInfo() — query token expiry durations from WSO2:
Calls GET /api/users/v1/me/session-info and returns the configured expiry durations for the current access and refresh tokens. Available from useAuth().
const { getSessionInfo } = useAuth();
const info = await getSessionInfo();
console.log(info.access_token_expires_in_seconds); // seconds remaining
console.log(info.refresh_token_expires_in_seconds); // seconds remainingWhat This Package Does
- Login via WSO2 App-Native Authentication API (PKCE S256, no browser redirect)
- Auto token refresh — proactive refresh 60 s before expiry + on app foreground
- Session restore on app launch — loads stored tokens, refreshes if expired
- Logout — revokes refresh token + terminates WSO2 SSO session
- Change Password — with optional OTP verification (SMS / email) controlled by WSO2 governance config
- Forgot Password — built-in WebView modal that opens inside the app, auto-closes when WSO2 redirects to the login page after a successful reset
- Human-readable errors — all WSO2 error codes mapped to plain English
- ALB sticky session — captures
AWSALBcookie from/authorizeand replays it on/authnso both requests hit the same WSO2 node behind an AWS Application Load Balancer - Secure token storage — pluggable adapter:
expo-secure-storefor Expo,react-native-keychainfor bare RN CLI
Requirements
| Requirement | Version | |---|---| | React Native | ≥ 0.76 | | React | ≥ 18 | | WSO2 Identity Server | 7.x | | Expo SDK (Expo only) | ≥ 52 | | react-native-webview (for Forgot Password modal) | ≥ 13.0.0 |
Installation
Expo (Managed or Bare)
npx expo install @antzsoft/wso2-auth-reactnative expo-crypto expo-secure-store react-native-webviewReact Native CLI (Bare)
npm install @antzsoft/wso2-auth-reactnative react-native-keychain react-native-webview
cd ios && pod install
expo-cryptois required for PKCE key generation. Hermes does not exposecrypto.getRandomValuesas a browser global, so the package usesexpo-cryptointernally.
react-native-webviewis required for the built-inopenPasswordRecovery()modal. If you only usegetPasswordRecoveryUrl()+Linking.openURLyou can skip it.
Quick Start — Expo
1. Configure WSO2 Console
In WSO2 Console → Applications → your app:
- Application type: Mobile Application
- Allowed grant types: Authorization Code
- Enable App-Native Authentication (under Sign-in Method)
- Token type: JWT (required for change-password OTP exclusion feature)
- Redirect URI: your custom scheme, e.g.
antzmobile://auth/callback
2. Wrap your app with AuthProvider
// App.tsx
import { AuthProvider } from '@antzsoft/wso2-auth-reactnative';
import { expoSecureStoreAdapter } from '@antzsoft/wso2-auth-reactnative/expo';
export default function App() {
return (
<AuthProvider
config={{
baseUrl: 'https://auth.antzsystems.com',
tenantDomain: 'dev', // your WSO2 tenant: "dev" | "uat" | "prod"
clientId: 'YOUR_CLIENT_ID', // from WSO2 Console → Applications → Protocol
redirectUri: 'antzmobile://auth/callback',
scopes: ['openid', 'profile', 'email', 'phone'],
storageAdapter: expoSecureStoreAdapter,
}}
>
<YourNavigator />
</AuthProvider>
);
}3. Use the useAuth hook
import { useAuth } from '@antzsoft/wso2-auth-reactnative';
export function LoginScreen() {
const { login, status, error } = useAuth();
async function handleLogin() {
try {
await login({ username: 'john', password: 'secret' });
// status becomes 'authenticated', navigate to home
} catch (err) {
// err.message is already human-readable
Alert.alert('Login failed', err.message);
}
}
return (
<Button title="Sign In" onPress={handleLogin} disabled={status === 'loading'} />
);
}4. Add Expo config plugins
In app.json / app.config.js:
{
"expo": {
"plugins": [
"expo-secure-store"
]
}
}Quick Start — React Native CLI (Bare)
1. Configure WSO2 Console
Same as Expo above.
2. Wrap your app with AuthProvider
// App.tsx
import { AuthProvider } from '@antzsoft/wso2-auth-reactnative';
import { rnKeychainAdapter } from '@antzsoft/wso2-auth-reactnative/rn';
export default function App() {
return (
<AuthProvider
config={{
baseUrl: 'https://auth.antzsystems.com',
tenantDomain: 'dev',
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'antzmobile://auth/callback',
scopes: ['openid', 'profile', 'email', 'phone'],
storageAdapter: rnKeychainAdapter,
}}
>
<YourNavigator />
</AuthProvider>
);
}3. Link native dependencies
cd ios && pod install4. Add expo-crypto as a dependency
Even in bare RN CLI projects, expo-crypto is used internally for PKCE:
npm install expo-crypto
cd ios && pod installAPI Reference
useAuth() hook
Returns the full auth context. Must be called inside <AuthProvider>.
const {
// State
status, // 'idle' | 'loading' | 'authenticated' | 'unauthenticated'
user, // AuthUser | null — decoded from JWT: sub, username, email, orgName, etc.
tokens, // TokenSet | null — accessToken, refreshToken, idToken, expiresAt
error, // string | null — last error message
// Actions
login, // (credentials: { username, password }) => Promise<void>
logout, // () => Promise<void>
sendChangePasswordOtp, // () => Promise<{ otpEnabled: boolean }>
changePassword, // (payload: ChangePasswordPayload) => Promise<void>
getSessionInfo, // () => Promise<SessionInfo> — token expiry details from WSO2
getPasswordRecoveryUrl, // () => string — raw URL (use with Linking.openURL if preferred)
openPasswordRecovery, // () => Promise<void> — opens built-in WebView modal
refreshTokens, // () => Promise<void>
clearError, // () => void
} = useAuth();useAccessToken() hook
Returns the current access token string. Throws if not authenticated.
const accessToken = useAccessToken();Feature Guide
Login
const { login } = useAuth();
await login({ username: '[email protected]', password: 'MyPassword123!' });Internally runs the full WSO2 App-Native flow:
POST /oauth2/authorizewithresponse_mode=direct+ PKCE challengePOST /oauth2/authnwith credentials (sticky cookie replayed for ALB affinity)POST /oauth2/tokenwith authorization code + PKCE verifier
Logout
const { logout } = useAuth();
await logout();
// Revokes refresh token + terminates WSO2 SSO session + clears local storageChange Password (OTP flow auto-detected)
const { sendChangePasswordOtp, changePassword } = useAuth();
// Step 1 — check if OTP is required
const { otpEnabled } = await sendChangePasswordOtp();
if (!otpEnabled) {
// OTP disabled for this app — change directly
await changePassword({ currentPassword: 'old', newPassword: 'new' });
} else {
// OTP sent to user's mobile/email — collect it, then:
await changePassword({ currentPassword: 'old', newPassword: 'new', otp: '123456' });
}Error codes you may receive from changePassword:
| Code | Meaning |
|---|---|
| INVALID_CREDENTIALS | Current password is wrong |
| INVALID_OTP | OTP code is incorrect |
| OTP_EXPIRED | OTP has expired (5 min TTL) |
| OTP_MAX_ATTEMPTS | 3 wrong codes — request a new OTP |
| PASSWORD_POLICY_VIOLATION | New password fails WSO2 policy |
All errors have .message set to a human-readable string.
Session Info
Call getSessionInfo() to retrieve the configured expiry durations for the current access and refresh tokens from WSO2 IS.
import { useAuth } from '@antzsoft/wso2-auth-reactnative';
import type { SessionInfo } from '@antzsoft/wso2-auth-reactnative';
export function SessionInfoCard() {
const { getSessionInfo } = useAuth();
const [info, setInfo] = React.useState<SessionInfo | null>(null);
async function handleFetch() {
try {
const result = await getSessionInfo();
setInfo(result);
} catch (err) {
console.error('Failed to fetch session info', err);
}
}
return (
<>
<Button title="Fetch Session Info" onPress={handleFetch} />
{info && (
<>
<Text>Access token expires in: {info.access_token_expires_in_seconds}s</Text>
<Text>Refresh token expires in: {info.refresh_token_expires_in_seconds}s</Text>
</>
)}
</>
);
}Returns: Promise<SessionInfo>
interface SessionInfo {
access_token_expires_at: number; // epoch timestamp (seconds)
access_token_expires_in_seconds: number; // seconds remaining
refresh_token_expires_at: number; // epoch timestamp (seconds)
refresh_token_expires_in_seconds: number; // seconds remaining
}Throws: Wso2ApiError — the access token is expired, invalid, or WSO2 returned an error.
Forgot Password
WSO2 password recovery is browser-based. The package ships a built-in WebView modal (WKWebView on iOS, Android WebView on Android) that:
- Slides up over the app — user never leaves
- Shows a toolbar with a ‹ Back button (web history only) and a single Close button
- Auto-closes when WSO2 redirects to the login page after a successful reset
- Is themed via the
themefield inAuthProviderconfig
Basic usage
Just call openPasswordRecovery() — no arguments needed:
import { useAuth } from '@antzsoft/wso2-auth-reactnative';
export function ForgotPasswordScreen() {
const { openPasswordRecovery } = useAuth();
async function handleReset() {
await openPasswordRecovery();
// Promise resolves when modal closes — either via Close button or auto-close after reset.
// Both paths resolve the same promise. To distinguish them, use openPasswordRecovery()
// with onComplete / onDismiss callbacks (see below).
Alert.alert('Done', 'If your password was reset, please sign in again.');
}
return <Button title="Reset Password" onPress={handleReset} />;
}Theming the modal
Pass a theme object in your AuthProvider config:
<AuthProvider
config={{
baseUrl: 'https://auth.antzsystems.com',
tenantDomain: 'dev',
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'antzmobile://auth/callback',
storageAdapter: expoSecureStoreAdapter,
theme: {
primaryColor: '#6C47FF', // toolbar background colour — default: "#6C47FF"
recoveryTitle: 'Reset Password', // toolbar title — default: "Reset Password"
closeLabel: 'Close', // close button (top-right) — default: "Close"
},
}}
>
<YourNavigator />
</AuthProvider>All theme fields are optional — defaults are used for anything not provided.
Using the raw URL instead
If you prefer to handle browser opening yourself (e.g. with Linking.openURL or a custom flow):
import { Linking } from 'react-native';
const { getPasswordRecoveryUrl } = useAuth();
await Linking.openURL(getPasswordRecoveryUrl());
// Opens: https://auth.antzsystems.com/t/dev/accounts/recovery?flowType=PASSWORD_RECOVERYInstalling react-native-webview
The built-in modal requires react-native-webview:
# Expo
npx expo install react-native-webview
# React Native CLI
npm install react-native-webview
cd ios && pod installToken Refresh
Token refresh is fully automatic — you do not need to call anything yourself. The package handles it in three ways:
1. On app launch (session restore)
When AuthProvider mounts, it loads the stored tokens from secure storage and immediately checks if the access token is expired. If it is, it silently refreshes before setting status = 'authenticated'. If the refresh token is also dead, the user is set to unauthenticated and must log in again.
2. Proactive timer — 60 seconds before expiry
As soon as tokens are set (after login or any refresh), a timer is scheduled to fire 60 seconds before the access token expires. This means the refresh happens in the background before the token actually expires — the user never hits a 401.
3. On app foreground
Every time the app comes back to the foreground (e.g. user switches back from another app), the package checks whether the current token has expired. If it has, it refreshes immediately. This covers the case where the app was backgrounded long enough for the token to expire.
What happens if refresh fails?
If the refresh token is expired or revoked (e.g. WSO2 session invalidated server-side), the package:
- Clears the stored tokens from secure storage
- Sets
status = 'unauthenticated' - Fires
onSessionExpiredcallback (if configured)
onSessionExpired callback (recommended):
Pass onSessionExpired in your AuthProvider config to react to session expiry with custom logic (analytics, alerts, navigation):
<AuthProvider
config={{
// ... connection config ...
onSessionExpired: () => {
// status is already 'unauthenticated' — RootNavigator switches to AuthStack automatically.
// Use this callback for additional UX: show an alert, log an analytics event, etc.
Alert.alert("Session expired", "Please log in again.");
},
}}
>Your app also reacts automatically via the status field in RootNavigator — the navigator switches to the auth stack regardless of whether onSessionExpired is configured.
The recommended pattern is a root navigator that switches between auth and app screens based on status:
// RootNavigator.tsx
import React from 'react';
import { useAuth } from '@antzsoft/wso2-auth-reactnative';
import { ActivityIndicator, View } from 'react-native';
export function RootNavigator() {
const { status } = useAuth();
if (status === 'idle' || status === 'loading') {
// Session restore in progress — show a splash/loading screen
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
// status === 'authenticated' → show app screens
// status === 'unauthenticated' → show login screen
// React Navigation re-renders automatically when status changes
return status === 'authenticated' ? <AppStack /> : <AuthStack />;
}This pattern handles all four cases automatically:
| status | Cause | What to show |
|---|---|---|
| idle | Before session restore runs | Splash / loading |
| loading | Session restore in progress | Splash / loading |
| authenticated | Valid tokens | App screens |
| unauthenticated | Not logged in, logout, or refresh token expired/revoked | Login screen |
If you use navigation.navigate or navigation.replace instead, make sure to handle the unauthenticated transition in a screen that is always mounted:
// In a screen that stays mounted (e.g. HomeScreen)
import { useEffect } from 'react';
import { useAuth } from '@antzsoft/wso2-auth-reactnative';
import { useNavigation } from '@react-navigation/native';
const { status } = useAuth();
const navigation = useNavigation();
useEffect(() => {
if (status === 'unauthenticated') {
navigation.replace('Login');
}
}, [status]);Note: When the refresh token expires mid-session,
statustransitions directly from'authenticated'to'unauthenticated'. Your navigator or screen will re-render and redirect to login automatically.
Manual refresh
refreshTokens() is available if you ever need to force a refresh manually, but this is rarely needed:
const { refreshTokens } = useAuth();
await refreshTokens();Configuring the refresh buffer
By default the proactive timer fires 60 seconds before the access token expires. You can change this via refreshBufferSeconds in AuthProvider config:
<AuthProvider
config={{
baseUrl: 'https://auth.antzsystems.com',
tenantDomain: 'dev',
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'antzmobile://auth/callback',
storageAdapter: expoSecureStoreAdapter,
refreshBufferSeconds: 30, // refresh 30s before expiry instead of 60s
}}
>Important: refreshBufferSeconds must be less than your WSO2 refresh token expiry time. If your refresh token expires in 180s and you set refreshBufferSeconds: 180, the refresh will be attempted the moment the access token is issued — effectively refreshing in a tight loop.
A safe rule of thumb: refreshBufferSeconds < refreshTokenExpiry / 2.
WSO2 token expiry settings
Make sure your WSO2 application has sensible expiry values. Set under Applications → Protocol → OAuth/OIDC:
| Token | Recommended | |---|---| | Access Token Expiry | 3600 s (1 hour) | | Refresh Token Expiry | 86400 s (24 hours) or longer |
If the access token expiry is very short (e.g. 60 s), the proactive timer will fire almost immediately after every login — not harmful but noisy.
Daily Expiry Check
The daily expiry check fires once per day at a configurable local time (default 5:00 AM). If the refresh token will expire within a configurable window (default 24 hours), onDailyExpiryWarning is called. This lets you proactively log the user out or show a warning before their session silently dies overnight.
Key differences from onSessionExpired:
| | onSessionExpired | onDailyExpiryWarning |
|---|---|---|
| Trigger | Refresh token already dead | Refresh token still alive but expiring soon |
| Timing | Immediately when detected | Once per day at configured local time |
| Auto-logout | No — status switches to unauthenticated | No — your callback decides |
| Purpose | Hard expiry cleanup | Proactive warning before next work day |
Enable and handle it:
<AuthProvider
config={{
// ... connection config ...
onSessionExpired: () => {
Alert.alert("Session expired", "Please log in again.");
},
// ── Daily expiry check ──────────────────────────────────────────────────
enableDailyExpiryCheck: true, // opt-in (default: false)
dailyCheckHour: 5, // 5 AM local time (default)
dailyCheckMinute: 0, // :00 (default)
expiryWarningWindowHours: 24, // warn if RT expires within 24 h (default)
onDailyExpiryWarning: () => {
// Option A — alert the user, let them decide
Alert.alert(
"Session expires today",
"Your session will expire today. Please log in again.",
[{ text: "Log out now", onPress: () => { /* call logout() */ } }, { text: "Later" }]
);
// Option B — log out immediately (silent)
// logout(); // call via ref or navigation — see App.tsx pattern
},
}}
>How it works on mobile (app killed vs backgrounded):
- App open continuously:
setTimeoutfires at the scheduled time — same as web. - App backgrounded: iOS/Android may suspend timers. The
AppState 'active'handler catches the missed check when the app foregrounds. - App fully killed and reopened:
setTimeoutdoes not survive a process kill. TheAppState 'active'handler runs on startup, readslastCheckDatefrom secure storage, and if today's scheduled time has passed and the check hasn't run yet, fires immediately.
lastCheckDate is persisted to secure storage so the check never fires twice on the same day, even across app restarts.
Auth State & User Info
const { status, user, tokens } = useAuth();
if (status === 'authenticated') {
console.log(user.username); // e.g. "john"
console.log(user.email); // e.g. "[email protected]"
console.log(user.orgName); // e.g. "Antz Systems"
console.log(tokens.accessToken);
console.log(tokens.expiresAt); // ms timestamp
}Adapters
Storage Adapters
expoSecureStoreAdapter — for Expo projects
import { expoSecureStoreAdapter } from '@antzsoft/wso2-auth-reactnative/expo';Uses expo-secure-store which maps to iOS Keychain and Android Keystore.
rnKeychainAdapter — for React Native CLI (bare) projects
import { rnKeychainAdapter } from '@antzsoft/wso2-auth-reactnative/rn';Uses react-native-keychain which maps to iOS Keychain and Android Keystore.
Custom storage adapter
import type { StorageAdapter } from '@antzsoft/wso2-auth-reactnative';
const myAdapter: StorageAdapter = {
getItem: async (key) => { /* return string | null */ },
setItem: async (key, value) => { /* store string */ },
removeItem: async (key) => { /* delete key */ },
};Configuration Reference
interface Wso2AuthConfig {
/** WSO2 IS base URL. No trailing slash. */
baseUrl: string;
/** Tenant slug: "dev" | "uat" | "prod" */
tenantDomain: string;
/** OAuth2 client_id from WSO2 Console → Applications → Protocol */
clientId: string;
/**
* Redirect URI registered in WSO2 Console.
* Must match exactly. In App-Native flow it is never actually followed,
* but WSO2 validates it is registered.
* Example: "antzmobile://auth/callback"
*/
redirectUri: string;
/** OAuth2 scopes. Default: ["openid", "profile", "email", "phone"] */
scopes?: string[];
/** Storage adapter for secure token persistence. Required — no default. */
storageAdapter: StorageAdapter;
/**
* Override the storage key. Useful for multi-account setups.
* Default: "antz_wso2_token_set"
*/
storageKey?: string;
/**
* How many seconds before access token expiry to proactively refresh.
* The refresh timer fires this many seconds early so the new token is
* ready before the old one is rejected by the server.
*
* Must be less than your WSO2 refresh token expiry time.
* Default: 60
*/
refreshBufferSeconds?: number;
/**
* Visual theme for the built-in PasswordRecoveryModal.
* All fields are optional — defaults are used for anything not provided.
*/
theme?: {
/** Toolbar background colour. Default: "#6C47FF" */
primaryColor?: string;
/** Toolbar title. Default: "Reset Password" */
recoveryTitle?: string;
/** Close button label (top-right). Default: "Close" */
closeLabel?: string;
};
// ── Session expiry callbacks ──────────────────────────────────────────────
/**
* Called when the session expires (refresh token dead or revoked).
* status is already 'unauthenticated' and tokens are cleared when this fires.
* Use for custom UX: alerts, analytics, navigation.
*
* Alternative: watch status === 'unauthenticated' in RootNavigator —
* both approaches work, use whichever fits your app's navigation setup.
*/
onSessionExpired?: () => void;
// ── Daily expiry check ────────────────────────────────────────────────────
/**
* Enable the daily scheduled refresh-token expiry check.
* When true, fires onDailyExpiryWarning once per day at dailyCheckHour:dailyCheckMinute
* (device local time) if the refresh token expires within expiryWarningWindowHours.
* Default: false
*/
enableDailyExpiryCheck?: boolean;
/** Local hour (0–23) for the daily check. Default: 5 (5 AM) */
dailyCheckHour?: number;
/** Local minute (0–59) for the daily check. Default: 0 */
dailyCheckMinute?: number;
/** Warn if refresh token expires within this many hours. Default: 24 */
expiryWarningWindowHours?: number;
/**
* Called when the daily check finds the refresh token expiring soon.
* The SDK does NOT log out — your handler decides the UX.
* Only fires when enableDailyExpiryCheck is true.
*/
onDailyExpiryWarning?: () => void;
}Error Handling
All errors thrown by this package are instances of Wso2ApiError:
import { Wso2ApiError } from '@antzsoft/wso2-auth-reactnative';
try {
await login({ username, password });
} catch (err) {
if (err instanceof Wso2ApiError) {
console.log(err.message); // Human-readable: "Incorrect username or password."
console.log(err.code); // WSO2 code: "ABA-60003"
console.log(err.httpStatus); // HTTP status: 401
}
}You can also use humanizeWso2Error directly:
import { humanizeWso2Error } from '@antzsoft/wso2-auth-reactnative';
const msg = humanizeWso2Error('INVALID_CREDENTIALS', undefined, undefined, 401);
// → "Current password is incorrect."WSO2 Console Setup Checklist
- [ ] Application type: Mobile Application
- [ ] Allowed grant types: Authorization Code
- [ ] Enable App-Native Authentication under Sign-in Method → Add Sign-in Step → App Native
- [ ] Access Token type: JWT (Applications → Protocol → OAuth/OIDC → Access Token)
- [ ] Add redirect URI (e.g.
antzmobile://auth/callback) - [ ] For change password OTP: enable under Resident IDP → Login & Registration → Change Password Settings
Troubleshooting
"Invalid authentication request" on login
→ The redirectUri in your config doesn't match exactly what's registered in WSO2 Console.
Login succeeds immediately without entering credentials (SUCCESS_COMPLETED)
→ Normal — WSO2 reused an existing SSO session. The package handles this automatically.
sendChangePasswordOtp always returns { otpEnabled: false }
→ OTP is not enabled in WSO2 Console for this tenant. Enable it under Resident IDP → Login & Registration → Change Password Settings.
changePassword throws "Current password is incorrect."
→ The currentPassword field is wrong. This is code INVALID_CREDENTIALS.
Token expires immediately → Check that your WSO2 application has a reasonable Access Token Expiry (default is 3600 seconds). Set under Applications → Protocol → OAuth/OIDC.
expo-crypto not found error on bare RN CLI
→ Run npm install expo-crypto && cd ios && pod install.
Forgot password modal is blank or shows an error page
→ Make sure react-native-webview is installed and native modules are linked (pod install on iOS).
Package Structure
@antzsoft/wso2-auth-reactnative
├── AuthProvider React context provider — wrap your root component
│ └── renders PasswordRecoveryModal internally (themed via config.theme)
├── useAuth() Primary hook — all state and actions
├── useAccessToken() Returns the raw access token string
├── PasswordRecoveryModal WebView modal component (also exported for direct use)
├── Wso2ApiError Error class with .code, .message, .httpStatus
├── humanizeWso2Error() Maps WSO2 error codes to human-readable strings
├── decodeJwt() Decodes a JWT string locally — returns payload claims or null
├── isTokenExpired() Returns true if the token's exp claim is in the past
├── jwtToUser() Converts decoded JWT claims to an AuthUser object
│
├── /expo Expo storage adapter (expo-secure-store)
│ └── expoSecureStoreAdapter
│
└── /rn React Native CLI storage adapter (react-native-keychain)
└── rnKeychainAdapterJWT utilities
import { decodeJwt, isTokenExpired } from '@antzsoft/wso2-auth-reactnative';
const { tokens } = useAuth();
// Decode all payload claims without a network call
const claims = decodeJwt(tokens!.accessToken);
// { sub: "...", roles: [...], exp: 1745612800, ... }
// Check if a TokenSet is already expired (with optional buffer in seconds)
if (isTokenExpired(tokens!, 60)) {
// token will expire within 60 seconds
}decodeJwt returns JwtPayload | null. Returns null if the string is not a valid JWT or decoding fails. isTokenExpired accepts a TokenSet and an optional bufferSeconds (default: 60).
License
MIT — © Antz Systems
