npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@instawork/oauth-login

v1.2.0

Published

Easily add Login with Instawork OAuth2 PKCE flow to your app.

Readme

@instawork/oauth-login

npm version TypeScript

Easily add "Login with Instawork" OAuth2 PKCE flow to your web application. This package provides a secure, TypeScript-first implementation of OAuth 2.0 with PKCE (Proof Key for Code Exchange) for client-side applications.

✨ Features

  • 🔒 Secure PKCE Implementation - Protects against authorization code interception attacks
  • 📘 TypeScript First - Full type safety with comprehensive interfaces and JSDoc
  • ⚛️ React Ready - Pre-built React components and hooks
  • 🎨 Customizable UI - Styled button component with customization options
  • 🚀 Easy Integration - Simple API with sensible defaults
  • 🛡️ Error Handling - Comprehensive error handling with detailed messages
  • 📱 Browser Support - Works in all modern browsers with Web Crypto API

📦 Installation

npm install @instawork/oauth-login

🚀 Quick Start

Generate client id

Follow instructions here: https://instawork.atlassian.net/wiki/spaces/EN/pages/4194697219/OAuth+Setup

React Component (Recommended)

import { InstaworkLoginButton, setupOAuthListener, handleOAuthRedirect } from '@instawork/oauth-login';
import { useEffect } from 'react';

function LoginPage() {
  // Set up listener for popup OAuth callback
  useEffect(() => {
    const removeListener = setupOAuthListener(
      (tokens) => {
        // Store tokens and redirect user
        localStorage.setItem('access_token', tokens.access_token);
        window.location.href = '/dashboard';
      },
      (error) => console.error('Login failed:', error)
    );
    return removeListener; // Cleanup on unmount
  }, []);

  return (
    <InstaworkLoginButton
      clientId="YOUR_CLIENT_ID"
      redirectUri="https://yourapp.com/oauth/callback"
      onError={(error) => console.error('Login failed:', error)}
    />
  );
}

// On your callback page (handles popup callback)
function CallbackPage() {
  React.useEffect(() => {
    handleOAuthRedirect({
      clientId: 'YOUR_CLIENT_ID',
      redirectUri: 'https://yourapp.com/oauth/callback',
      serverCallbackUrl: 'https://yourapp.com/api/oauth/callback'
    }).catch(error => {
      console.error('OAuth callback failed:', error);
    });
  }, []);

  return <div>Processing login...</div>;
}

Vanilla JavaScript/TypeScript

import { loginWithInstawork, setupOAuthListener, handleOAuthRedirect, isOAuthCallback } from '@instawork/oauth-login';

// Set up listener for popup OAuth callback (on main page)
setupOAuthListener(
  (tokens) => {
    console.log('Login successful!', tokens);
    localStorage.setItem('access_token', tokens.access_token);
    window.location.href = '/dashboard';
  },
  (error) => {
    console.error('Login failed:', error);
  }
);

// Initiate login
document.getElementById('login-btn')?.addEventListener('click', async () => {
  try {
    await loginWithInstawork({
      clientId: 'YOUR_CLIENT_ID',
      redirectUri: 'https://yourapp.com/oauth/callback'
      // scope: 'openid profile email' // Optional - only include if needed
    });
  } catch (error) {
    console.error('Login initiation failed:', error);
  }
});

// Handle callback (on your redirect page - callback.html)
if (isOAuthCallback()) {
  handleOAuthRedirect({
    clientId: 'YOUR_CLIENT_ID',
    redirectUri: 'https://yourapp.com/oauth/callback',
    serverCallbackUrl: 'https://yourapp.com/api/oauth/callback'
  }).catch(error => {
    console.error('OAuth callback failed:', error);
  });
}

📚 API Reference

loginWithInstawork(config)

Initiates the OAuth login flow by opening Instawork's authorization server in a popup window.

Note: The login page opens in a popup window to preserve the application state and localStorage. Make sure your application allows popups.

interface OAuthLoginConfig {
  clientId: string;        // Your OAuth client ID
  redirectUri: string;     // Registered redirect URI
  scope?: string;          // OAuth scopes (optional - no default)
  baseUrl?: string;        // Custom base URL (optional, defaults to https://www.instawork.com)
}

// Optional: Listen for authentication success in the parent window
window.addEventListener('message', (event) => {
  if (event.origin === window.location.origin && event.data.type === 'oauth-success') {
    console.log('User authenticated!', event.data.tokens);
    // Update your application state here
  }
});

handleOAuthRedirect(config)

Handles the OAuth callback and sends the authorization code to your server for token exchange.

Important: This function now requires a server-side endpoint to handle the actual token exchange with Instawork. This is more secure as it keeps your OAuth flow server-side.

interface OAuthRedirectConfig {
  clientId: string;           // Your OAuth client ID  
  redirectUri: string;        // Same redirect URI used in login
  serverCallbackUrl: string;  // Your server endpoint to handle token exchange
  baseUrl?: string;           // Custom base URL (optional, defaults to https://www.instawork.com)
}

interface OAuthTokenResponse {
  access_token: string;    // API access token
  token_type: string;      // Token type (usually 'Bearer')
  expires_in: number;      // Token expiration in seconds
  refresh_token?: string;  // Refresh token (if available)
  id_token?: string;       // JWT ID token with user info
  scope?: string;          // Granted scopes
}

InstaworkLoginButton

Pre-styled React button component with built-in OAuth handling.

interface InstaworkLoginButtonProps {
  clientId: string;
  redirectUri: string; 
  scope?: string;                              // Optional OAuth scopes
  baseUrl?: string;                            // Custom base URL (optional, defaults to https://www.instawork.com)
  children?: React.ReactNode;                  // Button text
  className?: string;                          // CSS class
  style?: React.CSSProperties;                 // Inline styles
  disabled?: boolean;                          // Disabled state
  loading?: boolean;                           // Loading state
  onClick?: () => void | Promise<void>;        // Pre-login callback
  onError?: (error: Error) => void;            // Error handler
}

useInstaworkLogin() Hook

React hook for managing OAuth login state.

const { login, isLoading, error, clearError } = useInstaworkLogin();

// Usage
const handleLogin = () => {
  login({
    clientId: 'YOUR_CLIENT_ID',
    redirectUri: 'https://yourapp.com/oauth/callback',
  });
};

setupOAuthListener(onSuccess, onError?)

Set up a listener to handle OAuth callbacks from popup windows. Call this once when your application initializes.

function setupOAuthListener(
  onSuccess: (tokens: OAuthTokenResponse) => void,
  onError?: (error: Error) => void
): () => void

// Usage
const removeListener = setupOAuthListener(
  (tokens) => {
    console.log('Authenticated!', tokens);
    // Handle successful authentication
  },
  (error) => {
    console.error('Auth failed:', error);
    // Handle authentication error
  }
);

// Returns a cleanup function to remove the listener
// Call this when your component unmounts
removeListener();

isOAuthCallback()

Utility function to check if the current page is handling an OAuth callback.

if (isOAuthCallback()) {
  // Handle the OAuth callback
}

🎨 Customization

Custom Button Styling

<InstaworkLoginButton
  clientId="YOUR_CLIENT_ID"
  redirectUri="https://yourapp.com/oauth/callback"
  style={{
    backgroundColor: '#custom-color',
    borderRadius: '8px',
    fontSize: '16px',
    padding: '16px 32px'
  }}
  className="my-custom-class"
>
  Sign in with Instawork
</InstaworkLoginButton>

Error Handling

const handleOAuthError = (error: Error) => {
  if (error.message.includes('Invalid state')) {
    // Handle CSRF attack attempt
    alert('Security error detected. Please try again.');
  } else if (error.message.includes('Token exchange failed')) {
    // Handle token exchange errors
    alert('Login failed. Please try again.');
  } else {
    // Handle other errors
    console.error('OAuth error:', error);
  }
};

🔧 Configuration

Environment Setup

  1. Register your application with Instawork to get your clientId
  2. Configure your redirect URI in the Instawork OAuth application settings
  3. That's it! The OAuth URLs are automatically configured for Instawork

Custom Base URL Configuration

For staging, development, or custom environments, you can specify a custom base URL:

// Using custom base URL for staging
await loginWithInstawork({
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'https://yourapp.com/oauth/callback',
  baseUrl: 'https://staging.instawork.com'
});

// Using custom base URL with port
await loginWithInstawork({
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'https://yourapp.com/oauth/callback',
  baseUrl: 'https://staging.instawork.com:8080'
});

// Also for callback handling (must use same base URL)
const tokens = await handleOAuthRedirect({
  clientId: 'YOUR_CLIENT_ID',
  redirectUri: 'https://yourapp.com/oauth/callback',
  baseUrl: 'https://staging.instawork.com:8080' // Same as login
});

Server-Side Implementation

The handleOAuthRedirect function sends the authorization code to your server for token exchange. You need to implement a server endpoint to handle this.

Request from Client:

POST /api/oauth/callback
Content-Type: application/json

{
  "code": "authorization_code_from_oauth",
  "redirect_uri": "https://yourapp.com/oauth/callback",
  "client_id": "your_client_id",
  "code_verifier": "pkce_code_verifier",
  "state": "state_value",
  "base_url": "https://www.instawork.com"
}

Example Server Implementation (Node.js/Express):

app.post('/api/oauth/callback', async (req, res) => {
  try {
    const { code, redirect_uri, client_id, code_verifier, base_url } = req.body;
    
    // Exchange authorization code for tokens
    const tokenUrl = `${base_url}/oauth2/token/`;
    const response = await fetch(tokenUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json'
      },
      body: new URLSearchParams({
        grant_type: 'authorization_code',
        code,
        redirect_uri,
        client_id,
        code_verifier,
        // client_secret: process.env.OAUTH_CLIENT_SECRET, // If using confidential client
      })
    });

    if (!response.ok) {
      const errorText = await response.text();
      return res.status(response.status).json({ 
        error: 'Token exchange failed',
        details: errorText 
      });
    }

    const tokens = await response.json();
    
    // Store tokens securely (e.g., in session, database)
    // Create user session
    req.session.accessToken = tokens.access_token;
    req.session.userId = parseJWT(tokens.id_token).sub;
    
    // Return tokens to client (or just success status)
    res.json(tokens);
  } catch (error) {
    console.error('OAuth callback error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

Example Server Implementation (Python/Flask):

@app.route('/api/oauth/callback', methods=['POST'])
def oauth_callback():
    try:
        data = request.get_json()
        code = data['code']
        redirect_uri = data['redirect_uri']
        client_id = data['client_id']
        code_verifier = data['code_verifier']
        base_url = data['base_url']
        
        # Exchange authorization code for tokens
        token_url = f"{base_url}/oauth2/token/"
        response = requests.post(token_url, data={
            'grant_type': 'authorization_code',
            'code': code,
            'redirect_uri': redirect_uri,
            'client_id': client_id,
            'code_verifier': code_verifier,
            # 'client_secret': os.environ.get('OAUTH_CLIENT_SECRET'),  # If using confidential client
        }, headers={
            'Accept': 'application/json'
        })
        
        if not response.ok:
            return jsonify({'error': 'Token exchange failed', 'details': response.text}), response.status_code
        
        tokens = response.json()
        
        # Store tokens securely (e.g., in session, database)
        session['access_token'] = tokens['access_token']
        session['user_id'] = parse_jwt(tokens['id_token'])['sub']
        
        # Return tokens to client
        return jsonify(tokens)
    except Exception as e:
        print(f'OAuth callback error: {e}')
        return jsonify({'error': 'Internal server error'}), 500

Popup Window Behavior

The OAuth login flow opens in a popup window to preserve your application's state and localStorage. This ensures:

  • ✅ PKCE codes remain accessible in the parent window
  • ✅ Your application state remains intact
  • ✅ Better user experience with automatic popup closure after authentication
  • ✅ Token exchange happens in the parent window context with proper localStorage access

Handling Popup Blockers:

If a popup blocker prevents the login window from opening, the library will throw an error. You can handle this gracefully:

try {
  await loginWithInstawork({ clientId, redirectUri, serverCallbackUrl });
} catch (error) {
  if (error.message.includes('popup')) {
    alert('Please allow popups to continue with login');
  }
}

Listening for Authentication Success:

Use the setupOAuthListener function to handle OAuth callbacks from the popup:

import { setupOAuthListener } from '@instawork/oauth-login';

// Set up the listener when your app initializes
const removeListener = setupOAuthListener(
  (tokens) => {
    console.log('User authenticated!', tokens);
    // Update your application state, redirect user, etc.
    localStorage.setItem('access_token', tokens.access_token);
    window.location.href = '/dashboard';
  },
  (error) => {
    console.error('Authentication failed:', error);
    alert('Login failed: ' + error.message);
  }
);

// Clean up when needed (e.g., component unmount)
// removeListener();

How It Works:

  1. Parent window calls loginWithInstawork()
    • Generates PKCE codes and state
    • Stores session in in-memory Map (indexed by state parameter)
    • Also stores in localStorage as fallback
  2. Popup window opens for OAuth authentication
  3. OAuth provider redirects to callback URL in the popup
  4. Popup detects oauth_popup_mode flag and extracts authorization code
  5. Popup sends code + state to parent via BroadcastChannel (or window.postMessage as fallback)
  6. Popup closes automatically
  7. Parent's setupOAuthListener receives the callback data
  8. Parent looks up session from in-memory Map using state parameter
  9. Parent exchanges code for tokens using the in-memory PKCE codes
  10. Your callback receives the tokens
  11. Session is cleaned up from memory automatically

Why In-Memory Storage?

Popup windows and parent windows have separate localStorage contexts in many browsers, even on the same origin. By storing PKCE codes in the parent window's memory (using a Map), we ensure they're always accessible when needed. The state parameter acts as the key to look up the correct session.

Benefits:

  • ✅ Works regardless of localStorage sharing behavior
  • ✅ More secure (data only in memory, never persisted)
  • ✅ Automatic cleanup of old sessions (>10 minutes)
  • ✅ No race conditions or timing issues

Security Considerations

  • Always validate the state parameter to prevent CSRF attacks (handled automatically by the client library)
  • Never expose your client secret - keep it on the server side only
  • Store tokens securely on the server (use secure HTTP-only cookies or server-side sessions)
  • Use HTTPS in production for all redirect URIs and API endpoints
  • The PKCE flow provides additional security for client-side applications
  • Implement proper session management and token refresh logic on your server
  • Consider implementing rate limiting on your OAuth callback endpoint
  • Validate postMessage origins - Always verify event.origin when listening for OAuth success messages

🛠️ Development

# Install dependencies
npm install

# Build the package
npm run build

# Development mode with file watching
npm run dev

📚 Examples

Check out the comprehensive examples in the examples/ directory:

Quick Start

# Choose your example
cd examples/vanilla-js  # or examples/react

# Install dependencies (auto-copies package files)
npm install

# Start development server
npm start

# Visit http://localhost:3000

Both examples include:

  • ✅ Complete setup instructions
  • ✅ Working OAuth login and callback flows
  • ✅ Error handling and validation
  • ✅ Production deployment guidance
  • ✅ Comprehensive documentation

📄 License

MIT © Instawork

🤝 Contributing

Issues and pull requests are welcome! Please read our contributing guidelines before submitting.

📞 Support

For OAuth application setup and API questions, please contact Instawork support.