
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@tailor-platform/auth-browser-client
Advanced tools
Browser client library for Tailor Platform authentication
Browser client library for Tailor Platform authentication using the "browser client" authentication flow.
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.
npm install @tailor-platform/auth-browser-client
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 } }'
User
(built-in default type - { id: string; [key: string]: any }
)query { me { id } }
(default)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
}
}`
});
⚠️ 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.
CustomUser
(custom type - { id: string; email: string; name?: string }
)query { me { id email name } }
(Required - must match the type fields)📝 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.
⚠️ CRITICAL: When using custom user types with
createAuthClient<CustomType>()
, you MUST provide a correspondingmeQuery
that fetches all the fields defined in your custom type interface.
Why this matters:
meQuery
to fetch user profile data from the Tailor Platform GraphQL APImeQuery
, the client will use the default query (query { me { id } }
)Key Requirements:
meQuery
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.
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;
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;
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();
});
The main function for creating an authentication client.
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.
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 } }')
}
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'
});
⚠️ 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:
me
fieldmeQuery
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.
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)
}
interface User {
id: string;
email: string;
name?: string;
[key: string]: any; // Additional user properties
}
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 };
}
interface AuthEvent {
type: 'auth_state_changed' | 'auth_error' | 'auth_loading';
data?: any;
}
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'
});
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
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
The library implements the Tailor Platform's "browser client" authentication flow with PKCE-like parameters for enhanced security.
Built-in CSRF protection through state parameter validation during authentication callback handling.
Automatic generation and inclusion of X-Tailor-Nonce
headers with each GraphQL request for additional CSRF protection.
Authentication tokens are stored in HTTPOnly cookies on the Tailor Platform server, preventing XSS attacks on token storage.
Token refresh and management are handled server-side through the HTTPOnly cookie mechanism, eliminating client-side token 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);
}
// ✅ 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
}
}
}`;
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);
}
}
});
// ✅ 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;
};
}
// ✅ 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;
}
// ✅ 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();
}
# Install dependencies
npm install
# Build CommonJS and ES Module versions
npm run build
# Clean build directory
npm run clean
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
Copyright © 2025 Tailor Inc.
FAQs
Browser client library for Tailor Platform authentication
The npm package @tailor-platform/auth-browser-client receives a total of 89 weekly downloads. As such, @tailor-platform/auth-browser-client popularity was classified as not popular.
We found that @tailor-platform/auth-browser-client demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 11 open source maintainers collaborating on the project.
Did you know?
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.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.