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.
- π 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
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:
- Account Linking Flow: Connect existing game accounts with OGS
- Web Auth Token: Enable sign-in with OGS (optional)
- Token Management: Automatic handling of token refresh
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
- 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
npm install @open-game-system/auth-kitimport { 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;
}
}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>
);
}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');
}
});// 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;
}// 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();// 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>
);
}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>
);
}// 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);
}
}Enable debug mode in the provider:
<AuthProvider debug={true}>
<YourApp />
</AuthProvider>- 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
- 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
- Handle all potential errors gracefully
- Provide clear error messages to users
- Implement retry mechanisms for transient failures
- Log errors for debugging
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;
}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.');
}- GitHub Issues: https://github.com/open-game-system/auth-kit/issues
- Discord Community: Join our Discord
- Email Support: hello@opengame.org
Auth Kit is licensed under the MIT License - see the LICENSE file for details.
Built with β€οΈ by the Open Game Collective