npm package: @jakobinn/expo-auth-utils
Description: Secure Google OAuth for web applications using the PKCE flow.
Latest version: 2.0.0

# @jakobinn/expo-auth-utils

A secure, reusable NPM package for handling Google OAuth in web applications using the PKCE (Proof Key for Code Exchange) flow.

## Features

- **Secure PKCE Flow**: Implements the OAuth 2.0 PKCE extension for enhanced security
- **TypeScript Support**: Full TypeScript definitions included
- **Browser Compatible**: Works in all modern browsers with Web Crypto API support
- **Session Storage**: Automatically manages state during OAuth redirects
- **CSRF Protection**: Built-in state parameter validation
- **Zero Dependencies**: Lightweight package with no external dependencies

## Installation

```bash
npm install @jakobinn/expo-auth-utils
```

## Quick Start

### 1. Initialize the OAuth Client

```typescript
import { GoogleWebAppOAuth } from '@jakobinn/expo-auth-utils';

const oauth = new GoogleWebAppOAuth({
  clientId: 'your-google-client-id.apps.googleusercontent.com',
  redirectUri: 'https://yourapp.com/auth/callback',
  scopes: ['openid', 'profile', 'email'],
});

### 2. Start the Sign-In Process

```typescript
// This will redirect the user to Google's login page
await oauth.signIn();
```

### 3. Handle the Callback

On your callback page (e.g., `/auth/callback`):

```typescript
import { GoogleWebAppOAuth } from '@jakobinn/expo-auth-utils';

const oauth = new GoogleWebAppOAuth({
  clientId: 'your-google-client-id.apps.googleusercontent.com',
  redirectUri: 'https://yourapp.com/auth/callback',
  scopes: ['openid', 'profile', 'email'],
});

try {
  const { code, codeVerifier } = oauth.handleRedirect();
  
  // Send code and codeVerifier to your server for token exchange
  const response = await fetch('/api/auth/google/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, codeVerifier }),
  });
  
  const { token, user } = await response.json();
  // Handle successful authentication
} catch (error) {
  // Handle authentication errors
  console.error('OAuth error:', error.message);
}

## Complete Example

### Login Page (`/login`)

```tsx
"use client";
import { GoogleWebAppOAuth } from '@jakobinn/expo-auth-utils';

const oauth = new GoogleWebAppOAuth({
  clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
  redirectUri: 'http://localhost:3000/auth/callback',
  scopes: ['openid', 'profile', 'email'],
});

export default function LoginPage() {
  const handleLogin = async () => {
    try {
      await oauth.signIn(); // Redirects to Google
    } catch (error) {
      console.error("OAuth initiation failed", error);
    }
  };

  return (
    <div>
      <h1>Sign In</h1>
      <button onClick={handleLogin}>
        Sign in with Google
      </button>
    </div>
  );
}

### Callback Page (`/auth/callback`)

```tsx
"use client";
import { useEffect } from 'react';
import { GoogleWebAppOAuth } from '@jakobinn/expo-auth-utils';

const oauth = new GoogleWebAppOAuth({
  clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
  redirectUri: 'http://localhost:3000/auth/callback',
  scopes: ['openid', 'profile', 'email'],
});

async function exchangeCodeOnServer(code: string, codeVerifier: string) {
  const response = await fetch('/api/auth/google/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, codeVerifier }),
  });

  if (!response.ok) {
    throw new Error('Failed to exchange code for token');
  }
  return response.json();
}

export default function AuthCallbackPage() {
  useEffect(() => {
    const processAuth = async () => {
      try {
        const { code, codeVerifier } = oauth.handleRedirect();
        console.log("Received code, exchanging for token...");
        
        const { jwt, user } = await exchangeCodeOnServer(code, codeVerifier);
        console.log("Authentication successful!", { jwt, user });
        
        // Redirect to dashboard or set user state
        window.location.href = '/dashboard';
      } catch (error) {
        console.error("OAuth callback failed:", error);
        window.location.href = '/login?error=' + encodeURIComponent(error.message);
      }
    };
    
    processAuth();
  }, []);

  return <div>Processing authentication...</div>;
}
```

## API Reference

### `GoogleWebAppOAuth`

#### Constructor

```typescript
new GoogleWebAppOAuth(config: WebGoogleOAuthConfig)
```

#### Configuration

```typescript
interface WebGoogleOAuthConfig {
  clientId: string;           // Your Google OAuth client ID
  redirectUri: string;        // Your app's callback URL
  scopes: string[];           // Array of OAuth scopes
}
```

#### Methods

##### `signIn(): Promise<void>`

Initiates the OAuth flow by redirecting the user to Google's authorization page.

##### `handleRedirect(): AuthResult`

Processes the OAuth callback and returns the authorization code and PKCE verifier.

```typescript
interface AuthResult {
  code: string;           // Authorization code from Google
  codeVerifier: string;   // PKCE code verifier for server exchange
}
```

## Security Features

- **PKCE Flow**: Prevents authorization code interception attacks
- **State Parameter**: CSRF protection with cryptographically secure random state
- **Session Storage**: Temporary storage that's cleared after use
- **Automatic Cleanup**: Stored data is automatically cleared after callback processing

## Browser Compatibility

This package requires browsers that support:
- Web Crypto API (`crypto.subtle`)
- `sessionStorage`
- `URLSearchParams`
- `TextEncoder`

Most modern browsers (Chrome 60+, Firefox 55+, Safari 11+, Edge 79+) support these features.

## Development

```bash
# Install dependencies
npm install

# Build the package
npm run build

# Development mode with watch
npm run dev

# Type checking
npm run type-check
```

## License

MIT