npm package: @nettie/franklin-id
Description: React component library for easily integrating Franklin/Genoox authentication (using Auth0) into your application.
Latest version: 0.1.2

# Franklin ID Auth Component (@nettie/franklin-id)

React component library for easily integrating Franklin/Genoox authentication (using Auth0) into your application.

## Installation

```bash
npm install @nettie/franklin-id # or yarn add @nettie/franklin-id, or bun install @nettie/franklin-id
# Make sure you have react and react-dom installed as peer dependencies
npm install react react-dom
```

## Prerequisites

Before using this component, ensure you have:

1.  **Configured an Auth0 Application**: Set up an application in your Auth0 dashboard (likely a Single Page Application type).
2.  **Configured an Auth0 API**: Set up an API in Auth0 that your application will interact with (e.g., `https://genoox.eu.auth0.com/api/v2/`). Note its **API Audience** identifier.
3.  **Registered Callback URL**: You need a backend endpoint (like the provided Supabase function example) that handles the OAuth code exchange. Add the **URL of this backend endpoint** (e.g., `https://<your-project-ref>.supabase.co/functions/v1/auth-callback`) to the "Allowed Callback URLs" in your Auth0 Application settings.
4.  **Obtained Credentials**: Note down your **Auth0 Domain**, **Client ID**, and the **API Audience** identifier.

## Usage

Import the `FranklinAuth` component using a **default import** and provide the necessary configuration props.

```tsx
import React, { useState } from 'react';
// Correct: Use a default import
import FranklinAuth from '@nettie/franklin-id';

// Configuration - Load sensitive values from environment variables or a config service
const AUTH0_DOMAIN = process.env.REACT_APP_AUTH0_DOMAIN || 'your-auth0-domain.auth0.com';
const AUTH0_CLIENT_ID = process.env.REACT_APP_AUTH0_CLIENT_ID || 'your_auth0_client_id';
// This MUST be the URL of your backend handler (e.g., Supabase function) registered in Auth0
const AUTH0_CALLBACK_URL = process.env.REACT_APP_AUTH0_CALLBACK_URL || 'https://<your-project-ref>.supabase.co/functions/v1/auth-callback';
const API_AUDIENCE = process.env.REACT_APP_API_AUDIENCE || 'https://your-api-audience.com/'; // Your Auth0 API Audience

function YourApplication() {
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [userProfile, setUserProfile] = useState<any>(null); // Store decoded ID token claims
  const [accessToken, setAccessToken] = useState<string | null>(null);
  const [authError, setAuthError] = useState<any>(null);

  // Function to parse ID token (basic example, use a library like jwt-decode for robustness)
  const parseJwt = (token: string) => {
    try {
      // Basic validation: check for 3 parts separated by dots
      if (token.split('.').length !== 3) return null;
      return JSON.parse(atob(token.split('.')[1]));
    } catch (e) {
      console.error("Error parsing JWT:", e);
      return null;
    }
  };

  const handleLoginSuccess = (tokens: { idToken: string, accessToken: string }) => {
    console.log("Authentication successful!");
    setIsAuthenticated(true);
    setAccessToken(tokens.accessToken); // Store access token in state (IMPORTANT: See token handling section)
    setUserProfile(parseJwt(tokens.idToken)); // Decode ID token for user info
    setAuthError(null);

    // Now you can use the accessToken to call your protected API
    // fetch('https://your.api.com/data', {
    //   headers: { Authorization: `Bearer ${tokens.accessToken}` }
    // }).then(...)
  };

  const handleLoginFailure = (error: any) => {
    console.error("Authentication failed:", error);
    setIsAuthenticated(false);
    setAccessToken(null);
    setUserProfile(null);
    setAuthError(error);
  };

  const handleLogout = () => {
    // Basic logout: clear local state
    setIsAuthenticated(false);
    setAccessToken(null);
    setUserProfile(null);
    setAuthError(null);
    // TODO: Implement full logout by redirecting to Auth0 logout endpoint
    // const logoutUrl = `https://${AUTH0_DOMAIN}/v2/logout?client_id=${AUTH0_CLIENT_ID}&returnTo=${window.location.origin}`;
    // window.location.href = logoutUrl;
  };

  return (
    <div>
      <h1>My Application</h1>
      {!isAuthenticated ? (
        <div>
          <p>Please log in.</p>
          <FranklinAuth
            auth0Domain={AUTH0_DOMAIN}
            clientId={AUTH0_CLIENT_ID}
            redirectUri={AUTH0_CALLBACK_URL} // Your backend callback handler URL
            audience={API_AUDIENCE}          // Your Auth0 API Audience
            onAuthSuccess={handleLoginSuccess}
            onAuthFailure={handleLoginFailure}
            // Optionally provide custom scopes or state:
            // scope="openid profile email read:custom_scope"
            // state="your_secure_random_state_value"
          />
          {authError && <p style={{ color: 'red' }}>Error: {authError.error_description || authError.error || 'Login failed'}</p>}
        </div>
      ) : (
        <div>
          <h2>Welcome, {userProfile?.name || 'User'}!</h2>
          {userProfile?.picture && (
            <img src={userProfile.picture} alt="Profile" width="50" style={{ borderRadius: '50%' }} />
          )}
          <p>You are logged in.</p>
          <button onClick={handleLogout}>Logout</button>
          {/* Display Access Token for debugging (REMOVE in production) */}
          {accessToken && (
            <div style={{ marginTop: '10px', wordBreak: 'break-all' }}>
              <strong>Access Token:</strong> <small>{accessToken}</small>
            </div>
          )}
          {/* Add components that need the access token here */}
          {/* Example: <UserProfileEditor accessToken={accessToken} /> */}
        </div>
      )}
    </div>
  );
}

export default YourApplication;
```

## Component Props

| Prop             | Type                                                    | Required | Description                                                                                                                               |
|------------------|---------------------------------------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------------|
| `auth0Domain`    | `string`                                                | Yes      | Your Auth0 tenant domain (e.g., `your-tenant.auth0.com`).                                                                                  |
| `clientId`       | `string`                                                | Yes      | The Client ID of your Auth0 Application.                                                                                                  |
| `redirectUri`    | `string`                                                | Yes      | The URL of **your backend callback handler** (e.g., Supabase function) that Auth0 should redirect to after login. This URL must be registered in your Auth0 Application's "Allowed Callback URLs". |
| `audience`       | `string`                                                | Yes      | The unique identifier (**API Audience**) of the API registered in Auth0 that you want to get an Access Token for.                       |
| `onAuthSuccess`  | `(tokens: { idToken: string, accessToken: string }) => void` | Yes      | Callback function executed on successful authentication. Receives an object containing the `idToken` and `accessToken`.                 |
| `onAuthFailure`  | `(error: any) => void`                                  | Yes      | Callback function executed on authentication failure. Receives the error object from Auth0.                                                  |
| `scope`          | `string`                                                | No       | Optional. Space-separated list of OAuth scopes to request. Defaults to extensive Franklin/Genoox scopes. Override if specific permissions are needed. |
| `state`          | `string`                                                | No       | Optional. A random string used for CSRF protection. If provided, your backend callback handler should generate and verify it. Defaults to a placeholder (implement proper state handling for production). |

## Handling Tokens

-   **`idToken` (ID Token)**:
    -   A JSON Web Token (JWT) containing claims about the authenticated user (e.g., name, email, picture).
    -   Intended for the client application (your React app) to understand *who* the user is.
    -   Can be decoded client-side to display user profile information.
    -   Auth0 validates this token during the code exchange in your backend callback handler.
-   **`accessToken` (Access Token)**:
    -   A token (often a JWT, but can be opaque) that proves the user has authorized your application to access specific resources on their behalf (defined by the `scope` requested).
    -   Intended for the **Resource Server** (your target API, e.g., `https://franklin.genoox.com/api/v2/...`).
    -   **Crucially, include this token in the `Authorization: Bearer <accessToken>` header when making requests from your application (or your backend) to your protected API.**
    -   **Security**: Do **NOT** store Access Tokens in `localStorage` or `sessionStorage` due to vulnerability to XSS attacks. Store them securely in application memory (React state, Context API, Redux, etc.) for the duration needed. Fetch new tokens when the application loads or the previous token expires (Auth0 handles session management to facilitate this).

## Development (of this package)

This package contains only the library code. For testing and development:

1.  Clone this repository.
2.  Install dependencies: `bun install`
3.  Build the package: `bun run build`
4.  Link the package locally: `npm link` (or `bun link`)
5.  In a separate test application (like the `test-auth-app` example), link the package: `npm link @nettie/franklin-id` (or `bun link @nettie/franklin-id`).
6.  Run the test application's development server (e.g., `bun run dev`). Changes in the `franklin-id` package will require rebuilding (`bun run build`) to be reflected in the test app.

```bash
# Example workflow:
# In ./franklin-id directory:
 bun install
 bun run build
 npm link # Or bun link

# In ../test-auth-app directory:
 bun install
 npm link @nettie/franklin-id # Or bun link @nettie/franklin-id
 bun run dev
```