Socket
Book a DemoInstallSign in
Socket

@tailor-platform/auth-browser-client

Package Overview
Dependencies
Maintainers
11
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tailor-platform/auth-browser-client

Browser client library for Tailor Platform authentication

latest
Source
npmnpm
Version
0.3.0
Version published
Maintainers
11
Created
Source

@tailor-platform/auth-browser-client

Browser client library for Tailor Platform authentication using the "browser client" authentication flow.

Overview

This library provides an authentication solution for browser-based applications integrating with the Tailor Platform. It implements a custom authentication flow designed specifically for the Tailor Platform's "browser client" type, as described in the Tailor Platform authentication documentation.

Important: This library is not a general-purpose OAuth 2.0/OpenID Connect client. It is specifically designed for Tailor Platform's browser client authentication flow, which uses HTTPOnly cookies for token management and includes Tailor-specific GraphQL integration features.

The library provides a functional approach with the createAuthClient function and has been designed with a modular architecture for maintainability.

Installation

npm install @tailor-platform/auth-browser-client

BASIC Usage

Step 1: Basic Example Using Default Type (User)

Start with a basic setup using the default User type. In this case, you don't need to specify type parameters.

import { createAuthClient } from '@tailor-platform/auth-browser-client';

// Use default User type (no type arguments)
const authClient = createAuthClient({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  redirectUri: 'https://your-app.com/callback', // Optional: defaults to current origin
  scope: 'openid profile email'
});

// Default meQuery is used: 'query { me { id } }'

Correspondence Between Default Type and meQuery

  • Type: User (built-in default type - { id: string; [key: string]: any })
  • meQuery: query { me { id } } (default)
  • Fetched Data: User ID only

Step 2: Using Custom Types

When you need more user information, use custom types in combination with corresponding meQuery.

import { createAuthClient, type AuthClient } from '@tailor-platform/auth-browser-client';

// Define custom user type
interface CustomUser {
  id: string;
  email: string;
  name?: string;
}

// Specify custom type and set corresponding meQuery
const authClient: AuthClient<CustomUser> = createAuthClient<CustomUser>({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  redirectUri: 'https://your-app.com/callback',
  scope: 'openid profile email',
  // meQuery corresponding to custom type
  meQuery: `query {
    me {
      id
      email
      name
    }
  }`
});

Correspondence Between Custom Type and meQuery

⚠️ IMPORTANT: When using custom user types, you MUST provide a corresponding meQuery that matches your type definition. Failure to do so will result in incomplete or incorrect user data.

  • Type: CustomUser (custom type - { id: string; email: string; name?: string })
  • meQuery: query { me { id email name } } (Required - must match the type fields)
  • Fetched Data: User ID, email, and name

📝 NOTE: The meQuery fields must correspond to the properties defined in your custom user type interface. Missing or mismatched fields will cause type safety issues and runtime errors.

Important Notes

Custom User Type and meQuery Dependency

⚠️ CRITICAL: When using custom user types with createAuthClient<CustomType>(), you MUST provide a corresponding meQuery that fetches all the fields defined in your custom type interface.

Why this matters:

  • The library uses the meQuery to fetch user profile data from the Tailor Platform GraphQL API
  • If you define a custom type but don't provide a matching meQuery, the client will use the default query (query { me { id } })
  • This mismatch will result in incomplete user data and potential runtime errors when accessing expected properties

Key Requirements:

  • Type-Query Alignment: Every field in your custom user interface must have a corresponding field in the meQuery
  • Required Fields: Ensure all non-optional fields in your type are included in the query
  • GraphQL Validity: The meQuery must be a valid GraphQL query structure

🚨 WARNING: Ignoring this dependency will cause your application to behave unexpectedly. User properties may be undefined even when you expect them to have values, leading to UI errors and poor user experience.

React Integration Examples

Step 1 Case (Using Default Type)

import React, { useEffect, useState } from 'react';
import { createAuthClient, AuthState } from '@tailor-platform/auth-browser-client';

const App: React.FC = () => {
  // Use default type (no type arguments)
  const [authClient] = useState(() => createAuthClient({
    clientId: 'your-client-id',
    appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
    redirectUri: window.location.origin + '/callback',
    scope: 'openid profile email'
  }));
  
  const [authState, setAuthState] = useState(authClient.getState());

  useEffect(() => {
    const unsubscribe = authClient.addEventListener((event) => {
      if (event.type === 'auth_state_changed') {
        setAuthState(event.data);
      }
    });

    return unsubscribe;
  }, [authClient]);

  if (authState.isLoading) {
    return <div>Loading...</div>;
  }

  if (authState.error) {
    return <div>Error: {authState.error}</div>;
  }

  if (!authState.isAuthenticated) {
    return (
      <div>
        <h1>Welcome</h1>
        <button onClick={() => authClient.login()}>
          Log In
        </button>
      </div>
    );
  }

  return (
    <div>
      <h1>Hello, User {authState.user?.id}</h1>
      <button onClick={() => authClient.logout()}>
        Log Out
      </button>
    </div>
  );
};

export default App;

Step 2 Case (Using Custom Type)

import React, { useEffect, useState } from 'react';
import { createAuthClient, type AuthClient, AuthState } from '@tailor-platform/auth-browser-client';

interface CustomUser {
  id: string;
  email: string;
  name?: string;
}

const App: React.FC = () => {
  // Specify custom type and set corresponding meQuery
  const [authClient] = useState(() => createAuthClient<CustomUser>({
    clientId: 'your-client-id',
    appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
    redirectUri: window.location.origin + '/callback',
    scope: 'openid profile email',
    meQuery: `query { me { id email name } }`
  }));
  
  const [authState, setAuthState] = useState<AuthState<CustomUser>>(authClient.getState());

  useEffect(() => {
    const unsubscribe = authClient.addEventListener((event) => {
      if (event.type === 'auth_state_changed') {
        setAuthState(event.data);
      }
    });

    return unsubscribe;
  }, [authClient]);

  if (authState.isLoading) {
    return <div>Loading...</div>;
  }

  if (authState.error) {
    return <div>Error: {authState.error}</div>;
  }

  if (!authState.isAuthenticated) {
    return (
      <div>
        <h1>Welcome</h1>
        <button onClick={() => authClient.login()}>
          Log In
        </button>
      </div>
    );
  }

  return (
    <div>
      <h1>Hello, {authState.user?.name || authState.user?.email}</h1>
      <button onClick={() => authClient.logout()}>
        Log Out
      </button>
    </div>
  );
};

export default App;

Vanilla JavaScript Example

import { createAuthClient } from '@tailor-platform/auth-browser-client';

const authClient = createAuthClient({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  redirectUri: window.location.origin + '/callback',
  scope: 'openid profile email'
});

// Listen for auth state changes
authClient.addEventListener((event) => {
  if (event.type === 'auth_state_changed') {
    const { isAuthenticated, user, error } = event.data;
    
    if (error) {
      document.getElementById('error-info').textContent = `Error: ${error}`;
      return;
    }
    
    if (isAuthenticated) {
      document.getElementById('user-info').textContent = 
        `Welcome, ${user.name || user.email}`;
      document.getElementById('login-btn').style.display = 'none';
      document.getElementById('logout-btn').style.display = 'block';
    } else {
      document.getElementById('user-info').textContent = 'Not signed in';
      document.getElementById('login-btn').style.display = 'block';
      document.getElementById('logout-btn').style.display = 'none';
    }
  }
});

// Login button
document.getElementById('login-btn').addEventListener('click', () => {
  authClient.login();
});

// Logout button
document.getElementById('logout-btn').addEventListener('click', () => {
  authClient.logout();
});

API Reference

createAuthClient

The main function for creating an authentication client.

Function Signature

createAuthClient<TUser = User>(config: AuthClientConfig): AuthClient<TUser>

Default Type: If no type parameter is provided, createAuthClient uses the built-in User interface as the default type. The User interface includes an id field and allows additional properties.

// Built-in User interface (default type)
interface User {
  id: string;
  [key: string]: any;
}

// Usage with default User type
const authClient = createAuthClient({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev'
});

// Usage with custom type
interface CustomUser {
  id: string;
  email: string;
  name?: string;
}

const customAuthClient = createAuthClient<CustomUser>({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev'
});

Type Safety: You can provide explicit type parameters for enhanced type safety with custom user interfaces.

Configuration

interface AuthClientConfig {
  clientId: string;        // OAuth client ID (required)
  appUri: string;         // Tailor Platform App URI (required)
  redirectUri?: string;   // Callback URL after authentication (optional, defaults to current origin)
  scope?: string;         // OAuth scopes (optional, default: 'openid profile email')
  audience?: string;      // OAuth audience (optional)
  meQuery?: string;       // Custom GraphQL query for user profile (optional, default: 'query { me { id } }')
}

Methods

getUser(): TUser | null

Returns the current user information.

const user = authClient.getUser();
if (user) {
  console.log('Current user:', user.email);
}

login(): Promise<void>

Initiates the OAuth authentication flow by redirecting to the authorization server.

await authClient.login();

logout(): Promise<void>

Logs out the user, revokes tokens, and clears authentication state.

await authClient.logout();

getState(): AuthState<TUser>

Returns the current authentication state.

const { isAuthenticated, user, isLoading, error, accessToken } = authClient.getState();

checkAuthStatus(): Promise<AuthState<TUser>>

Manually checks authentication status by verifying authentication cookies and fetching user profile.

const authState = await authClient.checkAuthStatus();

getAuthUrl(): Promise<string>

Generates an authentication URL without triggering redirect (useful for popup flows).

const authUrl = await authClient.getAuthUrl();
window.open(authUrl, 'auth-popup', 'width=500,height=600');

handleCallback(): Promise<void>

Handles OAuth callback after redirect (automatically called during initialization).

await authClient.handleCallback();

addEventListener(listener: AuthEventListener): () => void

Adds an event listener for authentication events. Returns an unsubscribe function.

const unsubscribe = authClient.addEventListener((event) => {
  console.log('Auth event:', event.type, event.data);
});

// Later, unsubscribe
unsubscribe();

configure(newConfig: Partial<AuthClientConfig>): void

Updates the client configuration.

authClient.configure({
  scope: 'openid profile email offline_access'
});

refreshTokens(): Promise<void>

Manually refreshes the authentication tokens using the refresh token grant flow.

// Manual token refresh
await authClient.refreshTokens();

// Listen for token refresh events
authClient.addEventListener((event) => {
  if (event.type === 'token_refresh') {
    console.log('Tokens refreshed:', event.data);
  }
});

Important Notes:

  • Tokens are managed server-side using HTTPOnly cookies
  • The method triggers a token_refresh event upon successful completion
  • Authentication state is automatically updated after successful refresh
  • Automatic token refresh occurs during checkAuthStatus() calls when tokens are expired

Custom User Profile Query

⚠️ CRITICAL REQUIREMENT: When using custom user types, you MUST provide a meQuery that corresponds exactly to your type definition.

You can customize the GraphQL query used to fetch user profile data during authentication by providing a meQuery option:

const authClient = createAuthClient<MyUser>({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  redirectUri: 'https://your-app.com/callback',
  scope: 'openid profile email',
  // Custom query to fetch additional user fields
  meQuery: `query {
    me {
      id
      email
      name
      createdAt
      avatar
      department
      roles
    }
  }`
});

Default Query: If no meQuery is specified, the client uses a minimal query: query { me { id } }

Custom Query Examples:

// Basic user info
meQuery: `query { me { id email name } }`

// Extended user profile
meQuery: `query {
  me {
    id
    email
    firstName
    lastName
    createdAt
    updatedAt
    profile {
      avatar
      bio
    }
  }
}`

// User with roles and permissions
meQuery: `query {
  me {
    id
    email
    name
    roles {
      id
      name
      permissions
    }
    department {
      id
      name
    }
  }
}`

Important Notes:

  • The query must be a valid GraphQL query that returns user data under the me field
  • The returned data structure MUST match your user type interface exactly
  • The query is executed during authentication status checks and after successful login
  • Failure to provide a matching meQuery for custom types will result in incomplete user data and runtime errors

🚨 DEPENDENCY WARNING: This is not an optional configuration when using custom types. The meQuery and your user type interface are tightly coupled and must be kept in sync.

Types

AuthState

interface AuthState<TUser> {
  user: TUser | null;              // Current user data
  isLoading: boolean;              // Loading state indicator
  isAuthenticated: boolean;        // Authentication status
  accessToken: string | null;      // Access token (when available)
  error: string | null;           // Error message (if any)
}

User Interface Example

interface User {
  id: string;
  email: string;
  name?: string;
  [key: string]: any; // Additional user properties
}

Type-Safe Usage with Custom User Types

The createAuthClient function requires explicit type parameters for enhanced type safety:

// Define your application-specific user type
interface MyUser {
  id: string;
  email: string;
  name?: string;
  avatar?: string;
  roles?: string[];
  department?: string;
}

// Create a type-safe AuthClient
const authClient = createAuthClient<MyUser>({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  redirectUri: window.location.origin + '/callback',
  scope: 'openid profile email',
  // meQuery corresponding to MyUser interface
  meQuery: `query {
    me {
      id
      email
      name
      avatar
      roles
      department
    }
  }`
});

// Now all methods return the correct types
const user: MyUser | null = authClient.getUser();
const state: AuthState<MyUser> = authClient.getState();

// Type-safe React hook example
function useAuth() {
  const [user, setUser] = useState<MyUser | null>(null);
  const [authClient] = useState(() => createAuthClient<MyUser>({
    clientId: 'your-client-id',
    appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
    meQuery: `query {
      me {
        id
        email
        name
        avatar
        roles
        department
      }
    }`
  }));
  
  useEffect(() => {
    const unsubscribe = authClient.addEventListener((event) => {
      if (event.type === 'auth_state_changed') {
        const authState = event.data as AuthState<MyUser>;
        if (authState.user) {
          // TypeScript knows user has MyUser properties
          console.log('User department:', authState.user.department);
          console.log('User roles:', authState.user.roles);
        }
      }
    });
    
    return unsubscribe;
  }, [authClient]);
  
  return { user, authClient };
}

AuthEvent

interface AuthEvent {
  type: 'auth_state_changed' | 'auth_error' | 'auth_loading' | 'token_refresh';
  data?: any;
}

Event Types:

  • auth_state_changed: Fired when authentication state changes (login, logout, user data update)
  • auth_error: Fired when authentication errors occur
  • auth_loading: Fired when authentication operations start/end
  • token_refresh: Fired when tokens are successfully refreshed (both manual and automatic)

Token Refresh Event Example:

authClient.addEventListener((event) => {
  if (event.type === 'token_refresh') {
    console.log('Token refresh successful:', event.data);
    // event.data = { success: true }
  }
});

Tailor Platform Integration

Environment Setup

For Tailor Platform integration, you'll typically need these configuration values:

const authClient = createAuthClient<User>({
  clientId: process.env.REACT_APP_TAILOR_CLIENT_ID!,
  appUri: process.env.REACT_APP_TAILOR_APP_URI!,
  redirectUri: `${window.location.origin}/callback`,
  scope: 'openid profile email'
});

Environment Variables

Create a .env file in your project root:

REACT_APP_TAILOR_CLIENT_ID=your-client-id
REACT_APP_TAILOR_APP_URI=https://your-tailor-app-xxxxxxxx.erp.dev

GraphQL Integration

The client uses HTTPOnly cookies for authentication, allowing you to use any GraphQL client library:

// Example with a popular GraphQL client (Apollo Client, urql, etc.)
// The authentication cookies will be automatically included

const query = `
  query GetUserData {
    me {
      id
      email
      name
    }
    userProjects {
      id
      name
      createdAt
    }
  }
`;

// Example with fetch
const response = await fetch('https://your-tailor-app-xxxxxxxx.erp.dev/query', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Tailor-Nonce': crypto.randomUUID() // CSRF protection
  },
  credentials: 'include', // Important: includes HTTPOnly cookies
  body: JSON.stringify({ query })
});

// The auth-browser-client handles authentication,
// while you're free to use any GraphQL client for API calls

Security Features

Tailor Platform Authentication Flow

The library implements the Tailor Platform's "browser client" authentication flow with PKCE-like parameters for enhanced security.

State Parameter Validation

Built-in CSRF protection through state parameter validation during authentication callback handling.

X-Tailor-Nonce Headers

Automatic generation and inclusion of X-Tailor-Nonce headers with each GraphQL request for additional CSRF protection.

HTTPOnly Cookies

Authentication tokens are stored in HTTPOnly cookies on the Tailor Platform server, preventing XSS attacks on token storage.

Server-side Token Management

Token refresh and management are handled server-side through the HTTPOnly cookie mechanism, eliminating client-side token handling.

Automatic Token Refresh

The library includes automatic token refresh functionality:

  • Automatic Refresh: Tokens are automatically refreshed during checkAuthStatus() calls when expired
  • Manual Refresh: Use refreshTokens() method for manual token refresh
  • Event Notification: Both automatic and manual refresh operations emit token_refresh events
  • Transparent Operation: Token refresh occurs transparently without user intervention
  • Fallback Handling: If automatic refresh fails, the user is marked as unauthenticated
// Automatic refresh is triggered during these operations:
await authClient.checkAuthStatus(); // Checks auth status and refreshes if needed

// Manual refresh can be called explicitly:
await authClient.refreshTokens(); // Manually refreshes tokens

// Both operations emit the same event:
authClient.addEventListener((event) => {
  if (event.type === 'token_refresh') {
    // Handle successful token refresh
    console.log('Tokens have been refreshed');
  }
});

Error Handling

The library provides comprehensive error handling with detailed error information:

authClient.addEventListener((event) => {
  if (event.type === 'auth_error') {
    const error = event.data;
    
    if (error.error === 'access_denied') {
      console.log('User cancelled authentication');
    } else if (error.error === 'invalid_request') {
      console.log('Invalid authentication request');
    } else if (error.error === 'invalid_client') {
      console.log('Client authentication failed');
    } else {
      console.error('Authentication error:', error);
    }
  }
});

// Error handling in state
const { error } = authClient.getState();
if (error) {
  // Handle authentication error
  console.error('Auth error:', error);
}

Common Error Scenarios

  • Invalid state parameter: CSRF protection triggered
  • Token exchange failed: Network or server issues during authentication flow
  • Authentication check failed: Issues validating existing sessions with Tailor Platform
  • Invalid redirect_uri: Configuration mismatch with Tailor Platform browser client settings

Best Practices

Type and Query Consistency

1. Design Type-First Approach

// ✅ RECOMMENDED: Define your user type first
interface AppUser {
  id: string;
  email: string;
  firstName?: string;
  lastName?: string;
  avatar?: string;
  roles: string[];
  department: {
    id: string;
    name: string;
  };
}

// Then create the corresponding meQuery
const meQuery = `query {
  me {
    id
    email
    firstName
    lastName
    avatar
    roles
    department {
      id
      name
    }
  }
}`;

2. Verify Type-Query Alignment

Create a utility function to validate the alignment:

// Utility function to verify type-query alignment during development
function validateTypeQueryAlignment<T>(
  sampleUser: T,
  actualUser: any
): void {
  const expectedFields = Object.keys(sampleUser as any);
  const actualFields = Object.keys(actualUser || {});
  
  const missingFields = expectedFields.filter(field => !actualFields.includes(field));
  
  if (missingFields.length > 0) {
    console.warn('⚠️ Type-Query mismatch detected!');
    console.warn('Missing fields in query response:', missingFields);
    console.warn('Update your meQuery to include these fields');
  }
}

// Usage in development
const authClient = createAuthClient<AppUser>({
  clientId: 'your-client-id',
  appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
  meQuery: `query {
    me {
      id
      email
      firstName
      lastName
      avatar
      roles
      department {
        id
        name
      }
    }
  }`
});

authClient.addEventListener((event) => {
  if (event.type === 'auth_state_changed' && event.data.user) {
    // Only run in development
    if (process.env.NODE_ENV === 'development') {
      const sampleUser: AppUser = {
        id: '',
        email: '',
        firstName: '',
        lastName: '',
        avatar: '',
        roles: [],
        department: { id: '', name: '' }
      };
      validateTypeQueryAlignment(sampleUser, event.data.user);
    }
  }
});

3. Incremental Development Approach

// ✅ Start with basic fields and gradually expand
// Phase 1: Basic user info
interface BasicUser {
  id: string;
  email: string;
}

// Phase 2: Add profile information
interface ProfileUser extends BasicUser {
  firstName?: string;
  lastName?: string;
  avatar?: string;
}

// Phase 3: Add organizational data
interface FullUser extends ProfileUser {
  roles: string[];
  department: {
    id: string;
    name: string;
  };
}

4. Handle Optional vs Required Fields

// ✅ Be explicit about optional vs required fields
interface WellDefinedUser {
  // Required fields (always present in your GraphQL schema)
  id: string;
  email: string;
  createdAt: string;
  
  // Optional fields (may be null/undefined)
  firstName?: string;
  lastName?: string;
  avatar?: string;
  
  // Arrays (provide default empty array structure)
  roles: string[];
  
  // Nested objects (define clear structure)
  profile: {
    bio?: string;
    location?: string;
  } | null;
}

5. Testing Your Configuration

// ✅ Create a test configuration to verify setup
async function testAuthConfiguration() {
  const authClient = createAuthClient<YourUserType>({
    clientId: 'test-client-id',
    appUri: 'https://your-tailor-app-xxxxxxxx.erp.dev',
    meQuery: `query {
      me {
        id
        email
        name
        // Add other fields that match your YourUserType interface
      }
    }`
  });
  
  // Test the meQuery independently
  try {
    const response = await fetch('https://your-workspace.tailorplatform.com/query', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      credentials: 'include',
      body: JSON.stringify({
        query: `your meQuery here`
      })
    });
    
    const data = await response.json();
    console.log('GraphQL query test result:', data);
    
    // Verify the structure matches your type
    if (data.data?.me) {
      console.log('✅ Query executed successfully');
      console.log('Available fields:', Object.keys(data.data.me));
    }
  } catch (error) {
    console.error('❌ Query test failed:', error);
  }
}

// Run during development
if (process.env.NODE_ENV === 'development') {
  testAuthConfiguration();
}

Development Workflow

  • Define User Type Interface: Start with the data structure you need
  • Create Matching meQuery: Write a GraphQL query that fetches all required fields
  • Test Query Independently: Verify the query works with your GraphQL endpoint
  • Implement Auth Client: Configure with the type and query
  • Add Debug Logging: Monitor the actual data received during development
  • Validate in Production: Ensure consistency across different environments

Development

Building the Library

# Install dependencies
npm install

# Build CommonJS and ES Module versions
npm run build

# Clean build directory
npm run clean

Project Structure

auth-browser-client/
├── src/
│   ├── AuthClient.ts      # Main authentication function (createAuthClient)
│   ├── index.ts           # Entry point and exports
│   ├── types/             # Type definitions
│   │   ├── auth.ts        # Authentication-related types
│   │   └── config.ts      # Configuration types
│   ├── internal/          # Internal implementation modules
│   │   ├── store.ts       # State management
│   │   ├── auth-operations.ts  # Authentication operations
│   │   ├── event-system.ts     # Event handling system
│   │   ├── config-management.ts # Configuration management
│   │   └── initialization.ts   # Initialization logic
│   └── utils/             # Utility functions
├── dist/                  # Built files (generated)
│   ├── index.js           # CommonJS build
│   ├── index.d.ts         # TypeScript definitions
│   └── esm/               # ES Modules build
├── package.json           # Package configuration
├── tsconfig.json          # TypeScript config (CommonJS)
├── tsconfig.esm.json      # TypeScript config (ES Modules)
└── README.md              # This file

Browser Compatibility

  • Modern Browsers: Chrome 80+, Firefox 74+, Safari 13.1+, Edge 80+
  • ES2022 Features: BigInt, Dynamic imports, Nullish coalescing, Optional chaining, Error.cause, crypto.randomUUID
  • Required APIs: Fetch API, Crypto.subtle (for PKCE), URLSearchParams

Copyright © 2025 Tailor Inc.

Keywords

authentication

FAQs

Package last updated on 16 Sep 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts