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.4.6

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

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'

Your app reacts to this via the status field — no callbacks or events needed.

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.


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;
  };
}

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
│
├── /expo                     Expo storage adapter (expo-secure-store)
│   └── expoSecureStoreAdapter
│
└── /rn                       React Native CLI storage adapter (react-native-keychain)
    └── rnKeychainAdapter

License

MIT — © Antz Systems