npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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:

  • doRefresh no longer logs out on network failure: TypeError (no internet, DNS, ECONNREFUSED) and AbortError (timeout) are now distinguished from auth errors (4xx invalid_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 inner doRefresh catch 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 polls GET /api/users/v1/me/session-info every 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 + fires onSessionExpired.
  • Paused automatically via AppState when 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_CHECK survives logout: clearStorage() no longer removes antz_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 in AuthProvider config. Fires when the SDK detects the refresh token is dead (proactive timer or foreground check). status is already 'unauthenticated' and tokens are cleared when this fires. Use for custom UX: alerts, analytics, navigation. RootNavigator still switches stacks automatically via status — this callback is additive.

  • Daily expiry check — new opt-in feature (enableDailyExpiryCheck: true). Fires onDailyExpiryWarning once 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 (lastCheckDate persisted in secure storage).

  • Refresh token expiry stored after logingetSessionInfo() 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 on logout().

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 remaining

What 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 AWSALB cookie from /authorize and replays it on /authn so both requests hit the same WSO2 node behind an AWS Application Load Balancer
  • Secure token storage — pluggable adapter: expo-secure-store for Expo, react-native-keychain for 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-webview

React Native CLI (Bare)

npm install @antzsoft/wso2-auth-reactnative react-native-keychain react-native-webview
cd ios && pod install

expo-crypto is required for PKCE key generation. Hermes does not expose crypto.getRandomValues as a browser global, so the package uses expo-crypto internally.

react-native-webview is required for the built-in openPasswordRecovery() modal. If you only use getPasswordRecoveryUrl() + Linking.openURL you 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 install

4. 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 install

API 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:

  1. POST /oauth2/authorize with response_mode=direct + PKCE challenge
  2. POST /oauth2/authn with credentials (sticky cookie replayed for ALB affinity)
  3. POST /oauth2/token with authorization code + PKCE verifier

Logout

const { logout } = useAuth();

await logout();
// Revokes refresh token + terminates WSO2 SSO session + clears local storage

Change 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 theme field in AuthProvider config

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_RECOVERY

Installing 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 install

Token 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:

  1. Clears the stored tokens from secure storage
  2. Sets status = 'unauthenticated'
  3. Fires onSessionExpired callback (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, status transitions 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: setTimeout fires 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: setTimeout does not survive a process kill. The AppState 'active' handler runs on startup, reads lastCheckDate from 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)
    └── rnKeychainAdapter

JWT 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