@guardhouse/react-native
v1.0.0-beta.1
Published
React Native SDK for Guardhouse OAuth PKCE, passkey auth, and secure token lifecycle
Maintainers
Readme
@guardhouse/react-native
React Native SDK for Guardhouse OAuth 2.0 Authorization Code + PKCE, passkey authentication, and secure token lifecycle.
Installation
npm install @guardhouse/react-nativePeer dependencies
react >=18.0.0react-native >=0.70.0react-native-quick-crypto >=0.7.0(optional, if Web Crypto is unavailable)
Required native modules
react-native-keychain– secure token storagereact-native-inappbrowser-reborn– in-app browser auth sessions
npm install react-native-keychain react-native-inappbrowser-reborn
cd ios && pod installPlatform setup
iOS
- Add URL scheme to
ios/YourApp/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>com.yourapp</string>
</array>
</dict>
</array>- Add to
ios/Podfile:
pod 'RNInAppBrowser', :path => '../node_modules/react-native-inappbrowser-reborn'- Run
pod install.
Android
- Add intent filter to
android/app/src/main/AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="com.yourapp"
android:host="callback" />
</intent-filter>- Set
launchMode="singleTask"on your main activity (required to prevent task hijacking):
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
...>Quick start
import {
GuardhouseClient,
KeychainStorageAdapter,
} from "@guardhouse/react-native";
const client = new GuardhouseClient({
authority: "https://your-guardhouse-domain.com",
clientId: "your-client-id",
redirectUri: "com.yourapp://callback",
scope: "openid profile offline_access",
refreshTokenStorage: new KeychainStorageAdapter(),
browser: yourBrowserAdapter, // see Adapters section
});Login
const result = await client.loginWithBrowser({
ephemeralSession: true,
});
console.log(result.session.accessToken);Restore session on app launch
const restored = await client.restoreSession();
if (restored?.session) {
// User is authenticated
}Get access token (auto-refresh)
const token = await client.getAccessToken();Logout
await client.logout({
revoke: true,
revokeRefreshToken: true,
});Registration with returnUrl
If your registration endpoint expects a returnUrl query parameter, configure it like:
endpoints: {
registration: "/account/signup?returnUrl=",
}The SDK will inject the generated authorize URL into returnUrl and keep the flow inside the auth session (returns to your app, not Safari).
Passkey login (headless WebAuthn)
Provide a passkey adapter that wraps a native WebAuthn library (e.g., react-native-passkey):
import type { GuardhousePasskeyAdapter } from "@guardhouse/react-native";
const passkeyAdapter: GuardhousePasskeyAdapter = {
name: "MyPasskeyAdapter",
async get(options) {
// call native passkey library and return assertion
return nativePasskeyGet(options);
},
};
const client = new GuardhouseClient({
// ...
passkey: passkeyAdapter,
});
const result = await client.loginWithPasskey();Adapters
Browser adapter
Implements GuardhouseBrowserAdapter:
interface GuardhouseBrowserAdapter {
name?: string;
openAuthSession(
authorizationUrl: string,
redirectUri: string,
options?: { ephemeralSession?: boolean; timeoutMs?: number },
): Promise<{ url: string }>;
}Default: InAppBrowserAuthAdapter (uses react-native-inappbrowser-reborn).
For Expo, use expo-web-browser:
import * as WebBrowser from "expo-web-browser";
export const expoBrowserAdapter = {
name: "ExpoBrowserAdapter",
async openAuthSession(authorizationUrl, redirectUri, options) {
const result = await WebBrowser.openAuthSessionAsync(
authorizationUrl,
redirectUri,
{ preferEphemeralSession: options?.ephemeralSession ?? true },
);
if (result.type === "success" && result.url) {
return { url: result.url };
}
throw new Error(result.type === "cancel" ? "Cancelled" : "Auth failed");
},
};Storage adapters
KeychainStorageAdapter– usesreact-native-keychain(default for production)ChunkedSecureStore– wraps any adapter to split large values- Provide your own
GuardhouseStorageAdapterfor Expo Go or custom runtimes
interface GuardhouseStorageAdapter {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem(key: string): Promise<void>;
}Crypto adapter
PKCE requires SHA-256 and secure random. If your React Native runtime provides globalThis.crypto.subtle, no extra setup is needed. Otherwise, install react-native-quick-crypto and provide a CryptoAdapter:
import type { CryptoAdapter } from "@guardhouse/core";
const cryptoAdapter: CryptoAdapter = {
async sha256(data) {
// return Uint8Array hash
},
async randomBytes(count) {
// return Uint8Array
},
};
const client = new GuardhouseClient({
// ...
cryptoAdapter,
});API
GuardhouseClient
Constructor
new GuardhouseClient(config: GuardhouseClientConfig)Methods
loginWithBrowser(options?)– OAuth browser loginregisterWithBrowser(options?)– OAuth browser registrationloginWithPasskey(options?)– headless WebAuthn loginexchangeCodeForTokens(code, options?)– exchange auth codeapplyRedirectTokens(payload)– apply tokens from deep linkrefreshToken(options?)– refresh access tokenrestoreSession(options?)– restore and optionally refreshgetSession()– get current sessiongetAccessToken(options?)– get access token (auto-refresh)logout(options?)– logout and optionally revoke tokens
Types
interface GuardhouseClientConfig {
authority: string;
clientId: string;
redirectUri: string;
scope?: string;
audience?: string;
cryptoAdapter?: CryptoAdapter;
refreshTokenStorage?: GuardhouseStorageAdapter;
sessionStorage?: GuardhouseStorageAdapter;
browser?: GuardhouseBrowserAdapter;
passkey?: GuardhousePasskeyAdapter;
fetch?: FetchLike;
defaultEphemeralSession?: boolean;
userInfoOnLogin?: boolean;
endpoints?: Partial<GuardhouseClientEndpoints>;
chunkSize?: number;
}
interface GuardhouseSession {
accessToken: string;
refreshToken?: string;
idToken?: string;
tokenType: string;
scope?: string;
expiresAt: number;
user: User | null;
}
interface GuardhouseAuthResult {
session: GuardhouseSession;
tokenResponse: GuardhouseTokenResponse;
user: User | null;
appState?: Record<string, unknown>;
}Error handling
All errors extend GuardhouseError:
GuardhouseAuthError– authentication/protocol errorsGuardhouseNetworkError– HTTP/network failuresGuardhouseStorageError– storage layer failuresGuardhouseConfigurationError– config errors
import { GuardhouseAuthError } from "@guardhouse/react-native";
try {
await client.loginWithBrowser();
} catch (error) {
if (error instanceof GuardhouseAuthError) {
console.log(error.code, error.message, error.statusCode);
}
}Security notes
- Tokens stored in iOS Keychain / Android Keystore (via
react-native-keychain) - No AsyncStorage/SharedPreferences fallback
- PKCE with S256 enforced
- State parameter CSRF protection
- Deep link sanitization and strict redirect URI matching
- Android
launchMode="singleTask"prevents task hijacking
License
Apache-2.0
