diff --git a/docs.json b/docs.json index ea91f3b7..661f35d8 100644 --- a/docs.json +++ b/docs.json @@ -36,189 +36,181 @@ } }, "navigation": { - "tabs": [{ - "tab": "Auth", - "groups": [ - { - "group": "Overview", - "pages": [ - "index", - "overview/pricing", - "overview/bring-your-app-to-production", - { - "group": "Login Options", - "icon": "arrow-right-to-bracket", - "pages": [ - "overview/login-options", - "overview/passkeys" - ] - }, - "overview/faqs", - "overview/authentication-flows", - "overview/changelog" - ] - }, - { - "group": "AI Prompts", - "icon": "robot", - "pages": [ - "ai-prompts/overview", - "ai-prompts/nextjs", - "ai-prompts/react", + "tabs": [ + { + "tab": "Auth", + "groups": [ + { + "group": "Overview", + "pages": [ + "index", + "overview/pricing", + "overview/bring-your-app-to-production", + { + "group": "Login Options", + "icon": "arrow-right-to-bracket", + "pages": ["overview/login-options", "overview/passkeys"] + }, + "overview/faqs", + "overview/authentication-flows", + "overview/changelog" + ] + }, + { + "group": "AI Prompts", + "icon": "robot", + "pages": [ + "ai-prompts/overview", + "ai-prompts/nextjs", + "ai-prompts/react", + { + "group": "Python", + "icon": "python", + "pages": [ + "ai-prompts/python/fastapi", + "ai-prompts/python/flask", + "ai-prompts/python/django" + ] + }, + { + "group": "Web3", + "icon": "wallet", + "pages": ["ai-prompts/web3/solana", "ai-prompts/web3/ethereum"] + }, + { + "group": "No-code Platforms", + "icon": "wand-magic-sparkles", + "pages": [ + "ai-prompts/no-code/lovable", + "ai-prompts/no-code/bolt", + "ai-prompts/no-code/v0", + "ai-prompts/no-code/replit" + ] + } + ] + }, + { + "group": "Integration", + "pages": [ + "integration/react", + "integration/nextjs", + "integration/vanillajs", + { + "group": "Node.JS", + "icon": "node-js", + "pages": [ + "integration/nodejs", + "integration/nodejs/express", + "integration/nodejs/hono", + "integration/nodejs/fastify" + ] + }, + { + "group": "Python", + "icon": "python", + "pages": [ + "integration/python", + "integration/python/fastapi", + "integration/python/flask", + "integration/python/django" + ] + }, + "integration/other", + "integration/error-codes" + ] + }, + { + "group": "Web3", + "pages": [ + "web3/embedded-wallets", + "web3/ethereum-evm", + "web3/solana" + ] + }, + { + "group": "Libraries & Tools", + "icon": "code", + "pages": ["libraries/auth-verify"] + }, + { + "group": "Mobile", + "pages": [ + "mobile/installation", + "mobile/quick-start", + "mobile/authentication-only", + "mobile/web3-wallet", + "mobile/api-reference" + ] + }, + { + "group": "Guides", + "pages": ["guides/add-auth-to-mcp"] + } + ], + "global": { + "anchors": [ { - "group": "Python", - "icon": "python", - "pages": [ - "ai-prompts/python/fastapi", - "ai-prompts/python/flask", - "ai-prompts/python/django" - ] + "anchor": "About us", + "href": "https://www.civic.com/about", + "icon": "circle-info" }, { - "group": "Web3", - "icon": "wallet", - "pages": [ - "ai-prompts/web3/solana", - "ai-prompts/web3/ethereum" - ] + "anchor": "Blog", + "href": "https://www.civic.com/blog", + "icon": "newspaper" }, { - "group": "No-code Platforms", - "icon": "wand-magic-sparkles", - "pages": [ - "ai-prompts/no-code/lovable", - "ai-prompts/no-code/bolt", - "ai-prompts/no-code/v0", - "ai-prompts/no-code/replit" - ] + "anchor": "Join Our Community", + "href": "https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw", + "icon": "slack" } ] - }, - { - "group": "Integration", - "pages": [ - "integration/react", - "integration/nextjs", - "integration/vanillajs", - { - "group": "Node.JS", - "icon": "node-js", - "pages": [ - "integration/nodejs", - "integration/nodejs/express", - "integration/nodejs/hono", - "integration/nodejs/fastify" - ] - }, - { - "group": "Python", - "icon": "python", - "pages": [ - "integration/python", - "integration/python/fastapi", - "integration/python/flask", - "integration/python/django" - ] - }, - { - "group": "Mobile", - "icon": "mobile-screen-button", - "pages": [ - "integration/mobile/react-native" - ] - }, - "integration/other", - "integration/error-codes" - ] - }, - { - "group": "Web3", - "pages": [ - "web3/embedded-wallets", - "web3/ethereum-evm", - "web3/solana" - ] - }, - { - "group": "Libraries & Tools", - "icon": "code", - "pages": [ - "libraries/auth-verify" - ] - }, - { - "group": "Guides", - "pages": [ - "guides/add-auth-to-mcp" - ] } - ], - "global": { - "anchors": [ + }, + { + "tab": "Labs", + "groups": [ { - "anchor": "About us", - "href": "https://www.civic.com/about", - "icon": "circle-info" + "group": " ", + "pages": ["labs", "labs/flask-status", "labs/feedback"] }, { - "anchor": "Blog", - "href": "https://www.civic.com/blog", - "icon": "newspaper" + "group": "Flasks", + "pages": [ + "labs/projects/mcp-hub", + "labs/projects/x402-mcp", + "labs/projects/guardrail-proxy", + "labs/projects/bodyguard", + "labs/projects/passthrough-proxy", + "labs/projects/civic-knowledge" + ] }, { - "anchor": "Join Our Community", - "href": "https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw", - "icon": "slack" + "group": "Concepts", + "pages": [ + "labs/concepts/mcp", + "labs/concepts/guardrails", + "labs/concepts/prompt-injection", + "labs/concepts/auth-strategies", + "labs/concepts/hooks", + "labs/concepts/rag" + ] + }, + { + "group": "🔓Integration", + "pages": [ + "labs/private/getting-started", + "labs/private/mcp-hub", + "labs/private/x402-mcp", + "labs/private/guardrail-proxy", + "labs/private/bodyguard", + "labs/private/passthrough-proxy", + "labs/private/civic-knowledge" + ] } ] } - },{ - "tab": "Labs", - "groups": [ - { - "group": " ", - "pages": [ - "labs", - "labs/flask-status", - "labs/feedback" - ] - }, - { - "group": "Flasks", - "pages": [ - "labs/projects/mcp-hub", - "labs/projects/x402-mcp", - "labs/projects/guardrail-proxy", - "labs/projects/bodyguard", - "labs/projects/passthrough-proxy", - "labs/projects/civic-knowledge" - ] - }, - { - "group": "Concepts", - "pages": [ - "labs/concepts/mcp", - "labs/concepts/guardrails", - "labs/concepts/prompt-injection", - "labs/concepts/auth-strategies", - "labs/concepts/hooks", - "labs/concepts/rag" - ] - }, - { - "group": "🔓Integration", - "pages": [ - "labs/private/getting-started", - "labs/private/mcp-hub", - "labs/private/x402-mcp", - "labs/private/guardrail-proxy", - "labs/private/bodyguard", - "labs/private/passthrough-proxy", - "labs/private/civic-knowledge" - ] - } - ] - }] + ] }, "logo": { "light": "/logo/light.png", diff --git a/index.mdx b/index.mdx index cb8e4fd5..95262179 100644 --- a/index.mdx +++ b/index.mdx @@ -132,7 +132,7 @@ Choose your framework for instructions on how to integrate Civic Auth into your Python - + React Native diff --git a/mobile/api-reference.mdx b/mobile/api-reference.mdx new file mode 100644 index 00000000..03054650 --- /dev/null +++ b/mobile/api-reference.mdx @@ -0,0 +1,424 @@ +--- +title: "API Reference" +icon: "book" +description: "Complete API reference for React Native Civic Auth integration." +public: true +--- + +## AuthContext API + +### AuthContextType + +Main authentication context interface for both authentication-only and Web3 integrations. + +```typescript +interface AuthContextType { + state: AuthState; + signIn?: () => Promise; + signOut?: () => Promise; + web3Client?: Web3Client | null; // Only available in Web3 integration +} +``` + +### AuthState + +Authentication state object containing user session information. + +```typescript +interface AuthState { + isLoading: boolean; + isAuthenticated: boolean; + user?: AuthUser; + accessToken?: string; + refreshToken?: string; + idToken?: string; + expiresIn?: number; +} +``` + +### AuthUser + +User profile information returned from Civic Auth. + +```typescript +interface AuthUser { + email?: string; + name: string; + picture?: string; + sub: string; // Unique user identifier +} +``` + +### Methods + +#### `signIn()` + +Initiates the OAuth2 authentication flow. + +**Returns:** `Promise` + +**Example:** +```typescript +const { signIn } = useContext(AuthContext); +await signIn(); +``` + +#### `signOut()` + +Signs out the user and clears the session. + +**Returns:** `Promise` + +**Example:** +```typescript +const { signOut } = useContext(AuthContext); +await signOut(); +``` + +--- + +## Web3Client API + + + Web3Client is only available when using the [Web3 Wallet Integration](/mobile/web3-wallet). + + +### Web3Client Interface + +Main interface for blockchain operations. + +```typescript +interface Web3Client { + solana: SolanaWeb3Client; // Solana wallet operations + connected: boolean; // Connection status + createWallets(): Promise; // Create embedded wallets + disconnect(): Promise; // Disconnect and cleanup +} +``` + +### SolanaWeb3Client Interface + +Solana-specific wallet operations. + +```typescript +interface SolanaWeb3Client { + readonly address: string; // Wallet public key + + // Core transaction methods + sendTransaction(address: string, amount: number): Promise; + signTransaction(transaction: Transaction, reason: string): Promise; + signMessage(message: string, reason: string): Promise; + + // Utility methods + getBalance(): Promise; + disconnect(): Promise; +} +``` + +### Methods + +#### `createWallets()` + +Creates embedded wallets for the authenticated user. + +**Returns:** `Promise` + +**Requirements:** +- User must be authenticated +- Valid ID token required + +**Example:** +```typescript +const { web3Client } = useContext(AuthContext); +if (!web3Client?.solana) { + const wallets = await web3Client?.createWallets(); + console.log('Wallets created:', wallets); +} +``` + +#### `sendTransaction(address, amount)` + +Sends SOL to a recipient address. Handles transaction creation, signing, and broadcasting. + +**Parameters:** +- `address` (string): Recipient's public key +- `amount` (number): Amount in SOL + +**Returns:** `Promise` - Transaction hash + +**Example:** +```typescript +const txHash = await web3Client?.solana?.sendTransaction( + "RecipientPublicKeyHere", + 0.5 // 0.5 SOL +); +console.log(`Transaction: ${txHash}`); +``` + +#### `signTransaction(transaction, reason)` + +Signs a custom Solana transaction. + +**Parameters:** +- `transaction` (Transaction): Solana transaction object +- `reason` (string): Human-readable reason shown to user + +**Returns:** `Promise` - Transaction signature + +**Example:** +```typescript +import { Transaction, SystemProgram, PublicKey } from "@solana/web3.js"; + +const transaction = new Transaction().add( + SystemProgram.transfer({ + fromPubkey: new PublicKey(web3Client.solana.address), + toPubkey: new PublicKey(recipientAddress), + lamports: 0.001 * 1e9, + }) +); + +const signature = await web3Client?.solana?.signTransaction( + transaction, + "Approve transfer" +); +``` + +#### `signMessage(message, reason)` + +Signs an arbitrary message for verification purposes. + +**Parameters:** +- `message` (string): Message to sign +- `reason` (string): Human-readable reason shown to user + +**Returns:** `Promise` - Base64 encoded signature + +**Example:** +```typescript +const message = "Verify wallet ownership"; +const signature = await web3Client?.solana?.signMessage( + message, + "Sign to verify your wallet" +); +console.log(`Signature: ${signature}`); +``` + +#### `getBalance()` + +Gets the current SOL balance of the wallet. + +**Returns:** `Promise` - Balance in SOL + +**Example:** +```typescript +const balance = await web3Client?.solana?.getBalance(); +console.log(`Balance: ${balance} SOL`); +``` + +#### `disconnect()` + +Disconnects the Web3 client and cleans up resources. + +**Returns:** `Promise` + +**Example:** +```typescript +await web3Client?.disconnect(); +``` + +--- + +## Configuration Objects + +### CivicWeb3ClientConfig + +Configuration for Web3 client initialization. + +```typescript +interface CivicWeb3ClientConfig { + solana: { + endpoint: string; // Solana RPC endpoint + }; +} +``` + +**Example:** +```typescript +const web3Config: CivicWeb3ClientConfig = { + solana: { + endpoint: "https://api.devnet.solana.com", + }, +}; +``` + +### AuthRequestConfig + +Configuration for OAuth2 authentication. + +```typescript +interface AuthRequestConfig { + clientId: string; + scopes: string[]; + redirectUri: string; + authorizationEndpoint: string; + tokenEndpoint: string; + userInfoEndpoint?: string; + endSessionEndpoint?: string; +} +``` + +**Example:** +```typescript +import { makeRedirectUri } from "expo-auth-session"; + +const authConfig: AuthRequestConfig = { + clientId: "your-client-id", + scopes: ["openid", "profile", "email"], + redirectUri: makeRedirectUri({ scheme: "your-app" }), + authorizationEndpoint: "https://auth.civic.com/oauth/auth", + tokenEndpoint: "https://auth.civic.com/oauth/token", + userInfoEndpoint: "https://auth.civic.com/oauth/userinfo", + endSessionEndpoint: "https://auth.civic.com/oauth/session/end", +}; +``` + +--- + +## Hooks + +### `useWeb3Client(config, idToken)` + +React hook for initializing and managing Web3 client. + +**Parameters:** +- `config` (CivicWeb3ClientConfig): Web3 configuration +- `idToken` (string | undefined): User's ID token from authentication + +**Returns:** `Web3Client | null | undefined` + +**Example:** +```typescript +import { useWeb3Client } from "@civic/react-native-auth-web3"; + +const web3Config = { + solana: { + endpoint: "https://api.devnet.solana.com", + }, +}; + +const web3Client = useWeb3Client(web3Config, authState.idToken); +``` + +--- + +## Error Handling + +### Common Error Types + +#### Authentication Errors + +```typescript +// Handle auth session errors +if (response?.type === "error") { + console.error("Authentication error:", response.error); + // Handle specific errors: + // - "access_denied": User denied access + // - "invalid_request": Malformed request + // - "server_error": Server-side error +} + +if (response?.type === "cancel") { + console.log("User cancelled authentication"); +} +``` + +#### Web3 Errors + +```typescript +try { + const txHash = await web3Client?.solana?.sendTransaction(address, amount); +} catch (error) { + console.error("Transaction failed:", error); + // Common errors: + // - Insufficient balance + // - Invalid recipient address + // - Network connectivity issues + // - User rejected transaction +} +``` + +#### Wallet Creation Errors + +```typescript +try { + const wallets = await web3Client?.createWallets(); +} catch (error) { + console.error("Wallet creation failed:", error); + // Common causes: + // - Missing or invalid ID token + // - Network connectivity issues + // - Service temporarily unavailable +} +``` + +--- + +## TypeScript Support + +All interfaces and types are fully typed for TypeScript projects. Import types from the respective packages: + +```typescript +// Authentication-only types +import { AuthRequestConfig } from "expo-auth-session"; + +// Web3 types +import type { + Web3Client, + SolanaWeb3Client, + CivicWeb3ClientConfig +} from "@civic/react-native-auth-web3"; + +// Solana types +import type { Transaction, PublicKey } from "@solana/web3.js"; +``` + +--- + +## Migration Guide + +### From Authentication Only to Web3 + +To upgrade from authentication-only to Web3 integration: + +1. **Install additional packages:** + ```bash + npm install @civic/react-native-auth-web3 @solana/web3.js + ``` + +2. **Add Web3 configuration:** + ```typescript + const web3Config = { + solana: { + endpoint: "https://api.devnet.solana.com", + }, + }; + ``` + +3. **Initialize Web3 client:** + ```typescript + const web3Client = useWeb3Client(web3Config, authState.idToken); + ``` + +4. **Complete native setup** as described in [Web3 Wallet Integration](/mobile/web3-wallet) + +### Breaking Changes + +- None currently, as Web3 integration is additive to authentication-only functionality + +--- + +## Support + +- **Documentation Issues:** [GitHub Issues](https://github.com/civicteam/civic-auth-examples/issues) +- **Developer Community:** [Slack Channel](https://join.slack.com/t/civic-developers/shared_invite/zt-37tv9fyo7-aDT43mUjOFQwdQFmfZLTRw) +- **Example Code:** [GitHub Repository](https://github.com/civicteam/civic-auth-examples/tree/main/packages/mobile/react-native-expo) \ No newline at end of file diff --git a/mobile/authentication-only.mdx b/mobile/authentication-only.mdx new file mode 100644 index 00000000..94acce8a --- /dev/null +++ b/mobile/authentication-only.mdx @@ -0,0 +1,439 @@ +--- +title: "Authentication Only" +icon: "user-check" +description: "Implement OAuth2 authentication in React Native without Web3 functionality." +public: true +--- + +## Overview + +For apps that only need user authentication without wallet functionality. This approach uses standard OAuth2/OIDC flow to authenticate users and access their profile information. + +**What you get:** +- User sign-in/sign-out +- Access to user profile (email, name, picture) +- Access and ID tokens for API calls +- Session management + +**What you don't get:** +- Blockchain wallet functionality +- Transaction signing +- Crypto payments + +## Installation + +Before starting, complete the [Installation & Setup](/mobile/installation) guide. + +For authentication-only integration, you only need these packages: + +```bash +npx expo install expo-auth-session expo-web-browser +``` + +## OAuth2 Configuration + +Civic Auth uses standard OAuth2/OIDC endpoints: + +- **Authorization**: `https://auth.civic.com/oauth/auth` +- **Token**: `https://auth.civic.com/oauth/token` +- **UserInfo**: `https://auth.civic.com/oauth/userinfo` +- **Scopes**: `openid profile email` + +## App Scheme Configuration + +Before implementing authentication, you need to configure your app scheme properly for mobile redirects. + +### 1. Configure app.json/app.config.js + +Add your app scheme to your Expo configuration: + +```json app.json +{ + "expo": { + "name": "Your App Name", + "scheme": "your-app", + // ... other config + } +} +``` + +Or in `app.config.js`: + +```js app.config.js +export default { + expo: { + name: "Your App Name", + scheme: "your-app", + // ... other config + }, +}; +``` + +### 2. Register URL Scheme (iOS) + +If using bare workflow, also add to your `ios/YourApp/Info.plist`: + +```xml +CFBundleURLTypes + + + CFBundleURLName + your-app + CFBundleURLSchemes + + your-app + + + +``` + +### 3. Register Intent Filter (Android) + +If using bare workflow, add to your `android/app/src/main/AndroidManifest.xml`: + +```xml + + + + + + + + + + +``` + + + Make sure to replace `"your-app"` with your actual app scheme throughout your configuration. The scheme should be unique and match across all configurations. + + +## Implementation + +### 1. Basic Authentication Flow + +React Native applications can integrate with Civic Auth using OAuth2/OIDC-compatible libraries. Popular options include: + +- [**Expo AuthSession**](https://docs.expo.dev/versions/latest/sdk/auth-session/) (Recommended) +- [**react-native-app-auth**](https://github.com/FormidableLabs/react-native-app-auth) + +### 2. Authentication Flow Steps + +The implementation follows a standard OAuth2 authorization code flow with PKCE: + +1. User initiates sign-in +2. App opens Civic Auth in a WebView +3. User authenticates with Civic +4. App receives authorization code through redirect +5. App exchanges code for tokens +6. App fetches user information + +### 3. Example AuthContext + +Here's a complete authentication context using Expo AuthSession: + +```typescript +import React, { createContext, useEffect, useMemo, useReducer } from "react"; +import { AuthRequestConfig, useAuthRequest, makeRedirectUri } from "expo-auth-session"; +import * as WebBrowser from "expo-web-browser"; + +// Auth configuration +const authConfig = { + clientId: "your-client-id", + scopes: ["openid", "profile", "email"], + redirectUri: makeRedirectUri({ scheme: "your-app" }), // Use makeRedirectUri for proper scheme handling + authorizationEndpoint: "https://auth.civic.com/oauth/auth", + tokenEndpoint: "https://auth.civic.com/oauth/token", + userInfoEndpoint: "https://auth.civic.com/oauth/userinfo", + endSessionEndpoint: "https://auth.civic.com/oauth/session/end", +}; + +interface AuthState { + isLoading: boolean; + isAuthenticated: boolean; + user?: AuthUser; + accessToken?: string; + idToken?: string; + expiresIn?: number; +} + +interface AuthUser { + email?: string; + name: string; + picture?: string; + sub: string; +} + +interface AuthAction { + type: string; + payload?: any; +} + +const initialState: AuthState = { + isLoading: false, + isAuthenticated: false, +}; + +export type AuthContextType = { + state: AuthState; + signIn?: () => Promise; + signOut?: () => Promise; +}; + +export const AuthContext = createContext({ + state: initialState, +}); + +// Close webview after auth completion +WebBrowser.maybeCompleteAuthSession(); + +export const AuthProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [request, response, promptAsync] = useAuthRequest( + { + clientId: authConfig.clientId, + scopes: authConfig.scopes, + redirectUri: authConfig.redirectUri, + usePKCE: true, // Required by Civic Auth + }, + { + authorizationEndpoint: authConfig.authorizationEndpoint, + tokenEndpoint: authConfig.tokenEndpoint, + }, + ); + + const [authState, dispatch] = useReducer( + (previousState: AuthState, action: AuthAction): AuthState => { + switch (action.type) { + case "SIGN_IN": + return { + ...previousState, + isAuthenticated: true, + accessToken: action.payload.access_token, + idToken: action.payload.id_token, + expiresIn: action.payload.expires_in, + }; + case "USER_INFO": + return { + ...previousState, + user: action.payload, + }; + case "SIGN_OUT": + return initialState; + default: + return previousState; + } + }, + initialState, + ); + + const authContext = useMemo( + () => ({ + state: authState, + signIn: async () => { + promptAsync(); + }, + signOut: async () => { + if (!authState.idToken) { + throw new Error("No idToken found"); + } + try { + const endSessionUrl = new URL(authConfig.endSessionEndpoint); + endSessionUrl.searchParams.append("client_id", authConfig.clientId); + endSessionUrl.searchParams.append("id_token_hint", authState.idToken); + endSessionUrl.searchParams.append( + "post_logout_redirect_uri", + authConfig.redirectUri, + ); + + const result = await WebBrowser.openAuthSessionAsync( + endSessionUrl.toString(), + authConfig.redirectUri, + ); + + if (result.type === "success") { + dispatch({ type: "SIGN_OUT" }); + } + } catch (e) { + console.warn(e); + } + }, + }), + [authState, promptAsync], + ); + + useEffect(() => { + const getToken = async ({ + code, + codeVerifier, + redirectUri, + }: { + code: string; + redirectUri: string; + codeVerifier?: string; + }) => { + try { + const response = await fetch(authConfig.tokenEndpoint, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + grant_type: "authorization_code", + client_id: authConfig.clientId, + code, + code_verifier: codeVerifier || "", + redirect_uri: redirectUri, + }).toString(), + }); + if (response.ok) { + const payload = await response.json(); + dispatch({ type: "SIGN_IN", payload }); + } + } catch (e) { + console.warn(e); + } + }; + + if (response?.type === "success") { + const { code } = response.params; + getToken({ + code, + codeVerifier: request?.codeVerifier, + redirectUri: authConfig.redirectUri || "", + }); + } else if (response?.type === "error") { + console.warn("Authentication error: ", response.error); + } + }, [dispatch, request?.codeVerifier, response]); + + useEffect(() => { + const fetchUserInfo = async () => { + try { + const response = await fetch(authConfig.userInfoEndpoint, { + headers: { Authorization: `Bearer ${authState.accessToken}` }, + }); + if (response.ok) { + const payload = await response.json(); + dispatch({ type: "USER_INFO", payload }); + } + } catch (e) { + console.warn("Failed to fetch user info:", e); + } + }; + + if (authState.isAuthenticated && authState.accessToken) { + fetchUserInfo(); + } + }, [authState.isAuthenticated, authState.accessToken]); + + return ( + {children} + ); +}; +``` + +### 4. Using the AuthContext + +```tsx +import React, { useContext } from "react"; +import { View, Text, Button } from "react-native"; +import { AuthContext } from "./AuthProvider"; + +export default function App() { + return ( + + + + ); +} + +function AuthScreen() { + const { state, signIn, signOut } = useContext(AuthContext); + + if (state.isAuthenticated) { + return ( + + Welcome, {state.user?.name}! + Email: {state.user?.email} +