Skip to content

πŸ” A lightweight SDK implementing the Account Linking Protocol. Enables web games to integrate with OGS authentication while maintaining their own identity and user management.

Notifications You must be signed in to change notification settings

open-game-system/auth-kit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

1 Commit
Β 
Β 

Repository files navigation

πŸ” Auth Kit

Add account linking to your web games in minutes

Auth Kit is a TypeScript library that implements the Account Linking Protocol from the Open Game System (OGS) specification. It enables web games to seamlessly integrate with the OGS platform's authentication system, allowing players to link their game accounts without sacrificing authentication independence.

npm version TypeScript MIT License

Features

  • πŸ”— Account Linking - Connect game accounts with OGS identities
  • πŸ”’ Authentication Independence - Maintain your own auth system
  • πŸͺ„ Single Sign-On - Enable seamless login via OGS
  • 🧩 Simple Integration - Easy-to-use API with minimal setup
  • βš›οΈ React Support - Ready-to-use React components and hooks
  • πŸ”„ Token Management - Automatic refresh token handling

Overview

Auth Kit provides a simple way to link player accounts in your game with their OGS identities. This enables cross-platform features while letting you maintain control over your own authentication system. The library handles:

  1. Account Linking Flow: Connect existing game accounts with OGS
  2. Web Auth Token: Enable sign-in with OGS (optional)
  3. Token Management: Automatic handling of token refresh

Architecture

Auth Kit follows the Account Linking Protocol from the OGS specification, providing a seamless integration between your game and the OGS platform:

flowchart TD
    subgraph "Your Game"
        GameAuth[Game Auth System]
        GameBackend[Game Backend]
        ServerSDK[Server SDK]
        ClientSDK[Client SDK]
    end
    
    subgraph "OGS Platform"
        OGSAuth[OGS Auth Service]
        OGSApi[OGS API]
    end
    
    subgraph "User Flow"
        UserGame[User in Game]
        UserOGS[User on OGS]
    end
    
    UserGame -->|1. Initiates link| ClientSDK
    ClientSDK -->|2. Requests token| GameBackend
    GameBackend -->|3. Gets link token| ServerSDK
    ServerSDK -->|4. Requests token| OGSApi
    OGSApi -->|5. Creates token| OGSAuth
    OGSApi -->|6. Returns token| ServerSDK
    ServerSDK -->|7. Returns token| GameBackend
    GameBackend -->|8. Returns URL| ClientSDK
    ClientSDK -->|9. Redirects to| UserOGS
    UserOGS -->|10. Authenticates| OGSAuth
    UserOGS -->|11. Confirms link| OGSAuth
    OGSAuth -->|12. Redirects back| UserGame
    GameBackend -->|13. Verifies link| OGSApi
    GameAuth -->|14. Stores link| GameBackend
Loading

Prerequisites

  • Game Authentication System - Your own auth system for player accounts
  • OGS API Key - Obtain through the certification process
  • Server Backend - Ability to make server-to-server API calls

Quick Start

1. Install the package

npm install @open-game-system/auth-kit

2. Server-Side Integration

import { createAuthClient } from '@open-game-system/auth-kit/server';

// Create a client with your API key
const authClient = createAuthClient({
  apiKey: 'YOUR_OGS_API_KEY'
});

// Request a link token
async function getAccountLinkToken(gameUserId) {
  try {
    const result = await authClient.createLinkToken({
      gameUserId,
      redirectUrl: 'https://yourgame.com/auth/callback'
    });
    
    return {
      linkToken: result.linkToken,
      linkUrl: result.linkUrl
    };
  } catch (error) {
    console.error('Failed to create link token:', error);
    throw error;
  }
}

// Verify the account link
async function verifyAccountLink(token) {
  try {
    const result = await authClient.verifyLinkToken({
      token
    });
    
    if (result.valid) {
      // Store the link in your database
      await storeAccountLink(result.gameUserId, result.ogsUserId);
      return true;
    }
    return false;
  } catch (error) {
    console.error('Failed to verify account link:', error);
    return false;
  }
}

3. Client-Side Integration

import { AuthProvider, useAuth } from '@open-game-system/auth-kit/react';
import { LinkButton } from '@open-game-system/auth-kit/react-ui';

// Wrap your app with the provider
function App() {
  return (
    <AuthProvider>
      <YourGame />
    </AuthProvider>
  );
}

// Use the hook in your components
function AccountSettings() {
  const { 
    isLinked,
    linkAccount,
    unlinkAccount,
    isLinking,
    linkError
  } = useAuth();
  
  const handleLinkClick = async () => {
    if (!isLinked) {
      // Get current user ID from your auth system
      const gameUserId = getCurrentUserId();
      
      // Initiate linking process
      await linkAccount(gameUserId);
    } else {
      // Unlink account
      await unlinkAccount();
    }
  };
  
  return (
    <div>
      <h2>Account Settings</h2>
      
      {/* Using built-in UI component */}
      <LinkButton />
      
      {/* Or using your own UI */}
      <button 
        onClick={handleLinkClick}
        disabled={isLinking}
      >
        {isLinking ? 'Linking...' : isLinked ? 'Unlink OGS Account' : 'Link OGS Account'}
      </button>
      
      {linkError && <p className="error">{linkError.message}</p>}
    </div>
  );
}

4. Setup Callback Endpoint

Create a callback endpoint to handle the redirect from OGS:

// Express example
app.get('/auth/callback', async (req, res) => {
  const { token, state } = req.query;
  
  if (!token) {
    return res.status(400).send('Missing token');
  }
  
  // Verify the link token
  const success = await verifyAccountLink(token);
  
  if (success) {
    res.redirect('/account?linked=success');
  } else {
    res.redirect('/account?linked=error');
  }
});

API Reference

Server SDK

// Create an auth client
const client = createAuthClient({ 
  apiKey: string,
  baseUrl?: string 
});

// Client interface
interface AuthClient {
  // Create an account link token
  createLinkToken(params: CreateLinkTokenParams): Promise<CreateLinkTokenResult>;
  
  // Verify an account link token
  verifyLinkToken(params: VerifyLinkTokenParams): Promise<VerifyLinkTokenResult>;
  
  // Create a web auth code
  createWebAuthCode(): Promise<CreateWebAuthCodeResult>;
  
  // Verify a web auth code
  verifyWebAuthCode(params: VerifyWebAuthCodeParams): Promise<VerifyWebAuthCodeResult>;
  
  // Refresh an access token
  refreshToken(params: RefreshTokenParams): Promise<RefreshTokenResult>;
}

// Parameters and results
interface CreateLinkTokenParams {
  // Your game's user ID
  gameUserId: string;
  
  // URL to redirect after linking
  redirectUrl: string;
  
  // Optional metadata
  metadata?: Record<string, any>;
}

interface CreateLinkTokenResult {
  // Token to use for linking
  linkToken: string;
  
  // URL to redirect user to
  linkUrl: string;
  
  // Token expiration time
  expiresAt: string;
}

interface VerifyLinkTokenParams {
  // Token from the callback
  token: string;
}

interface VerifyLinkTokenResult {
  // Whether the token is valid
  valid: boolean;
  
  // Your game's user ID
  gameUserId: string;
  
  // OGS user ID
  ogsUserId: string;
  
  // User's email (if available)
  email?: string;
  
  // When the link was created
  linkedAt: string;
}

interface VerifyWebAuthCodeParams {
  // Code from OGS app
  code: string;
}

interface VerifyWebAuthCodeResult {
  // Whether the code is valid
  valid: boolean;
  
  // OGS user ID
  ogsUserId: string;
  
  // User's email (if verified)
  email?: string;
  
  // Whether email is verified
  isVerified: boolean;
}

interface RefreshTokenParams {
  // Refresh token
  refreshToken: string;
}

interface RefreshTokenResult {
  // Operation success
  success: boolean;
  
  // New access token
  accessToken: string;
  
  // Token expiration in seconds
  expiresIn: number;
}

Client SDK

// React hook for auth
const {
  // Whether account is linked
  isLinked: boolean,
  
  // OGS user ID if linked
  ogsUserId: string | null,
  
  // User email if available
  email: string | null,
  
  // Start account linking process
  linkAccount: (gameUserId: string) => Promise<void>,
  
  // Unlink the account
  unlinkAccount: () => Promise<void>,
  
  // Whether linking is in progress
  isLinking: boolean,
  
  // Current error if any
  linkError: Error | null,
  
  // Sign in with OGS
  signInWithOGS: () => Promise<void>,
  
  // Check if signed in via OGS
  isOGSSignIn: boolean
} = useAuth();

Advanced Usage

Sign In with OGS (Single Sign-On)

// Server-side: Handle web auth code verification
app.post('/api/auth/ogs-verify', async (req, res) => {
  const { code } = req.body;
  
  try {
    const result = await authClient.verifyWebAuthCode({ code });
    
    if (result.valid) {
      // Check if user exists in your system
      let user = await findUserByOGSId(result.ogsUserId);
      
      if (!user) {
        // Create new user or prompt to link with existing account
        user = await createUserFromOGS(result.ogsUserId, result.email);
      }
      
      // Create session in your auth system
      const session = await createUserSession(user.id);
      
      res.json({
        success: true,
        token: session.token
      });
    } else {
      res.status(400).json({
        success: false,
        error: 'Invalid code'
      });
    }
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Verification failed'
    });
  }
});

// Client-side: Sign in with OGS
function SignInPage() {
  const { signInWithOGS, isOGSSignIn } = useAuth();
  
  const handleOGSSignIn = async () => {
    try {
      await signInWithOGS();
      // Redirect to dashboard
    } catch (error) {
      console.error('OGS sign in failed:', error);
    }
  };
  
  return (
    <div>
      <button onClick={handleOGSSignIn}>Sign in with OGS</button>
    </div>
  );
}

Custom UI Components

Build your own UI for account linking:

import { useAuth } from '@open-game-system/auth-kit/react';

function CustomLinkButton() {
  const { 
    isLinked, 
    linkAccount, 
    unlinkAccount, 
    isLinking, 
    ogsUserId,
    email
  } = useAuth();
  
  return (
    <div className="account-link-section">
      <h3>OGS Account</h3>
      
      {isLinked ? (
        <div className="linked-account">
          <p>Linked to OGS account: {email || ogsUserId}</p>
          <button 
            className="unlink-button"
            onClick={unlinkAccount}
          >
            Disconnect Account
          </button>
        </div>
      ) : (
        <div className="unlinked-account">
          <p>
            Link your account with OGS to enable notifications
            and TV casting features.
          </p>
          <button 
            className="link-button"
            onClick={() => linkAccount(getCurrentUserId())}
            disabled={isLinking}
          >
            {isLinking ? 'Connecting...' : 'Connect with OGS'}
          </button>
        </div>
      )}
    </div>
  );
}

Debugging

Server-Side Debugging

// Enable debug mode
const authClient = createAuthClient({
  apiKey: 'YOUR_API_KEY',
  debug: true
});

// Debug logs will be output to console

// Handling specific errors
try {
  await authClient.verifyLinkToken({ token });
} catch (error) {
  if (error.code === 'token_expired') {
    // Handle expired token
    console.log('Token has expired, request a new one');
  } else if (error.code === 'token_invalid') {
    // Handle invalid token
    console.log('Token is invalid');
  } else {
    // Handle other errors
    console.error('Verification failed:', error);
  }
}

Client-Side Debugging

Enable debug mode in the provider:

<AuthProvider debug={true}>
  <YourApp />
</AuthProvider>

Best Practices

1. Security Considerations

  • Never expose your API key in client-side code
  • Always verify link tokens on your server
  • Store account links securely in your database
  • Use HTTPS for all API requests

2. User Experience

  • Clearly explain the benefits of linking accounts
  • Provide a smooth linking flow with minimal friction
  • Allow users to unlink accounts easily
  • Display account linking status clearly

3. Error Handling

  • Handle all potential errors gracefully
  • Provide clear error messages to users
  • Implement retry mechanisms for transient failures
  • Log errors for debugging

Troubleshooting

Common Issues

Link Token Expiration

Link tokens expire after 15 minutes. If a user takes too long to complete the flow:

// Check if error is due to token expiration
if (error.code === 'token_expired') {
  // Request a new token
  const { linkToken, linkUrl } = await getAccountLinkToken(gameUserId);
  
  // Redirect user again
  window.location.href = linkUrl;
}

Account Already Linked

If a user tries to link an account that's already linked:

// Check if error is due to already linked account
if (error.code === 'account_already_linked') {
  // Show appropriate message
  showMessage('This account is already linked to an OGS account.');
}

Support and Community

License

Auth Kit is licensed under the MIT License - see the LICENSE file for details.


Built with ❀️ by the Open Game Collective

About

πŸ” A lightweight SDK implementing the Account Linking Protocol. Enables web games to integrate with OGS authentication while maintaining their own identity and user management.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published