Skip to content

Integrate Your Authentication System

Learn how to integrate your authentication system with Pika Framework, replacing the default mock authentication with your production authentication provider (OAuth, SAML, JWT, or custom).

By the end of this guide, you will:

  • Create a custom authentication provider for your auth system
  • Implement user authentication and token validation
  • Configure user types and roles for access control
  • Deploy your custom authentication to production

Before starting, ensure you have:

  • A running Pika installation (local or deployed)
  • Access to your authentication provider (OAuth, SAML, JWT, etc.)
  • Authentication provider credentials (client ID, secret, etc.)
  • Basic understanding of TypeScript and async/await patterns

Pika uses a two-tier data structure for user information:

T (Auth Data):

  • Sensitive authentication tokens and session data
  • Stored in encrypted cookies only (never database)
  • Available server-side only
  • Not accessible to agents or tools
  • Must be Record<string, string> or undefined

U (Custom Data):

  • Business-specific user information
  • Stored in encrypted cookies AND database
  • Available to agent tools (but NOT the agent itself)
  • Persists across sessions
  • Must be Record<string, string> or undefined

Create type definitions for your authentication and custom user data.

Location: apps/pika-chat/src/lib/server/auth-provider/types.ts

// Auth data - stored securely in cookies, never saved to database
export interface YourCustomAuthData {
accessToken: string;
refreshToken?: string;
expiresAt?: number;
// Add your auth-specific properties here
}
// Custom data - saved to database with user record
export interface YourCustomUserData {
email?: string;
companyId?: string;
companyName?: string;
accountId?: string;
// Add your custom user properties here
}

Implement your authentication provider by extending the AuthProvider<T, U> class.

Location: apps/pika-chat/src/lib/server/auth-provider/index.ts

import type { RequestEvent } from '@sveltejs/kit';
import type { AuthenticatedUser, AuthenticateResult } from 'pika-shared/types/chatbot/chatbot-types';
import { AuthProvider, NotAuthenticatedError, ForceUserToReauthenticateError } from '../auth/types';
import { redirect } from '@sveltejs/kit';
import type { YourCustomAuthData, YourCustomUserData } from './types';
export default class YourAuthProvider extends AuthProvider<YourCustomAuthData, YourCustomUserData> {
async authenticate(event: RequestEvent): Promise<AuthenticateResult<YourCustomAuthData, YourCustomUserData>> {
// Check if this is a login route
if (event.url.pathname.startsWith('/auth/login')) {
return this.startOAuthFlow(event);
}
// Extract auth token from cookies or headers
const authToken = this.extractAuthToken(event);
if (!authToken) {
// No token found - redirect to login
return { redirectTo: redirect(302, '/login') };
}
try {
// Validate token and get user data
const userData = await this.getUserFromAuthProvider(authToken);
const user = this.createAuthenticatedUser(userData, authToken);
return { authenticatedUser: user };
} catch (error) {
// Token invalid or expired
throw new NotAuthenticatedError('Invalid or expired token');
}
}
async validateUser(
event: RequestEvent,
user: AuthenticatedUser<YourCustomAuthData, YourCustomUserData>
): Promise<AuthenticatedUser<YourCustomAuthData, YourCustomUserData> | undefined> {
// Check if the user's access token is still valid
const accessToken = user.authData.accessToken;
try {
const isValid = await this.validateTokenWithProvider(accessToken);
if (isValid) {
return undefined; // Token is still valid
}
// Token expired - try to refresh
if (user.authData.refreshToken) {
try {
const newTokens = await this.refreshTokens(user.authData.refreshToken);
const updatedUser = { ...user };
updatedUser.authData = {
...updatedUser.authData,
accessToken: newTokens.access_token,
refreshToken: newTokens.refresh_token,
expiresAt: newTokens.expires_at
};
return updatedUser;
} catch (refreshError) {
throw new ForceUserToReauthenticateError('Token refresh failed');
}
}
throw new ForceUserToReauthenticateError('Token expired');
} catch (error) {
if (error instanceof ForceUserToReauthenticateError) {
throw error;
}
throw new ForceUserToReauthenticateError('Token validation failed');
}
}
private extractAuthToken(event: RequestEvent): string | null {
return event.cookies.get('your-auth-token') ||
event.request.headers.get('authorization')?.replace('Bearer ', '');
}
private async getUserFromAuthProvider(token: string): Promise<any> {
const response = await fetch('https://your-auth-provider.com/api/user', {
headers: { Authorization: `Bearer ${token}` }
});
if (!response.ok) {
throw new Error('Failed to get user data');
}
return response.json();
}
private createAuthenticatedUser(
userData: any,
token: string
): AuthenticatedUser<YourCustomAuthData, YourCustomUserData> {
return {
userId: userData.id,
firstName: userData.firstName,
lastName: userData.lastName,
// User type for access control
userType: userData.isEmployee ? 'internal-user' : 'external-user',
// Roles for permissions
roles: userData.roles || [],
// Custom data - saved to database
customData: {
email: userData.email,
companyId: userData.companyId,
companyName: userData.companyName,
accountId: userData.accountId
},
// Auth data - secure cookie only
authData: {
accessToken: token,
refreshToken: userData.refreshToken,
expiresAt: userData.expiresAt
},
features: {
instruction: {
type: 'instruction',
instruction: 'You are a helpful assistant.'
},
history: {
type: 'history',
history: true
}
}
};
}
}

Implement logic to assign user types and roles based on your authentication provider's data.

// Determine user type based on email domain
function determineUserType(email: string): 'internal-user' | 'external-user' {
const companyDomains = ['yourcompany.com', 'corp.yourcompany.com'];
const emailDomain = email.split('@')[1];
return companyDomains.includes(emailDomain) ? 'internal-user' : 'external-user';
}
// Extract roles from your auth provider
function extractUserRoles(userData: any): string[] {
const roles: string[] = [];
// Add Pika admin role for super admins
if (userData.isSuperAdmin) {
roles.push('pika:content-admin');
}
// Add custom business roles
if (userData.permissions?.includes('manage_team')) {
roles.push('team-manager');
}
return roles;
}
  1. Start your local development server:
Terminal window
cd apps/pika-chat
pnpm run dev
  1. Navigate to your application in a browser
  2. Verify the authentication flow:
    • Redirects to login when not authenticated
    • Successfully authenticates with valid credentials
    • Stores user data correctly
    • Maintains session across page refreshes

Before deploying to production:

Verify your authentication implementation:

"Type errors with generics"

  • Ensure you use extends AuthProvider<YourAuthData, YourCustomData> not implements
  • Don't redeclare generic parameters on your class

"Redirect loops"

  • Check that /login and /auth/* routes don't trigger authentication
  • Verify your extractAuthToken logic is correct

"Cookie issues"

  • Verify cookie domain configuration matches your deployment
  • Ensure SameSite and Secure flags are set correctly
  • Check cookie size limits if storing large auth data

"Token validation fails"

  • Enable debug logging with DEBUG_AUTH=true
  • Verify token format matches your auth provider
  • Check token expiration handling

Now that you have custom authentication: