Frontend Getting Started
The Boards frontend auth system provides a stable, provider-agnostic interface through the useAuth() hook and pluggable auth providers. This guide shows you how to set up authentication in your React application.
Installation
Install the core frontend package:
npm install @weirdfingers/boards
Then install your chosen auth provider package:
# For development (no auth)
# No additional package needed - NoAuthProvider included in core
# For JWT authentication
npm install @weirdfingers/auth-jwt
# For Supabase
npm install @weirdfingers/auth-supabase @supabase/supabase-js
# For Clerk
npm install @weirdfingers/auth-clerk @clerk/clerk-js
# For Auth0
npm install @weirdfingers/auth-auth0 @auth0/auth0-spa-js
Basic Setup
1. Create Auth Provider
Choose and configure your auth provider:
// No Auth (development only)
import { NoAuthProvider } from "@weirdfingers/boards";
const authProvider = new NoAuthProvider({
apiUrl: "http://localhost:8088/api",
tenantId: "default",
});
// JWT (production)
import { JWTAuthProvider } from "@weirdfingers/auth-jwt";
const authProvider = new JWTAuthProvider({
apiUrl: "http://localhost:8088/api",
tenantId: "my-company",
loginEndpoint: "/auth/login",
signupEndpoint: "/auth/signup",
});
// Supabase
import { SupabaseAuthProvider } from "@weirdfingers/auth-supabase";
const authProvider = new SupabaseAuthProvider({
url: process.env.NEXT_PUBLIC_SUPABASE_URL!,
anonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
tenantId: "my-company",
});
2. Wrap Your App
import { AuthProvider, createGraphQLClient } from "@weirdfingers/boards";
import { Provider } from "urql";
function App() {
// TODO(cleanup): if this is a functional component, then simply instantiating
// a variable for the client isnt what we want to do. State, probably?
// Create GraphQL client with auth integration
const graphqlClient = createGraphQLClient({
url: "http://localhost:8088/graphql",
auth: authProvider,
tenantId: "my-company",
});
return (
<AuthProvider provider={authProvider}>
<Provider value={graphqlClient}>
<MyApp />
</Provider>
</AuthProvider>
);
}
export default App;
3. Use in Components
import { useAuth } from "@weirdfingers/boards";
function MyComponent() {
const { user, status, signIn, signOut, signUp, refreshToken } = useAuth();
// Show loading state
if (status === "loading") {
return <div>Loading...</div>;
}
// Show sign in form
if (status === "unauthenticated") {
return (
<div>
<button onClick={() => signIn()}>Sign In</button>
<button onClick={() => signUp()}>Sign Up</button>
</div>
);
}
// Show authenticated content
return (
<div>
<div>
<img src={user.avatarUrl} alt="Avatar" />
<h2>Hello, {user.displayName}!</h2>
<p>{user.email}</p>
</div>
<button onClick={signOut}>Sign Out</button>
</div>
);
}
useAuth Hook API
The useAuth() hook provides a consistent interface regardless of auth provider:
interface AuthState {
// Current user (null if not authenticated)
user: User | null;
// Authentication status
status: "loading" | "authenticated" | "unauthenticated";
// Authentication methods
signIn: (credentials?: any) => Promise<void>;
signOut: () => Promise<void>;
signUp: (credentials?: any) => Promise<void>;
// Token management
refreshToken: () => Promise<void>;
getToken: () => Promise<string | null>;
}
interface User {
id: string;
email?: string;
displayName?: string;
avatarUrl?: string;
tenantId: string;
}
GraphQL Integration
The auth system automatically handles GraphQL authentication:
import { useQuery } from "urql";
function MyBoardsList() {
// Token automatically included in requests
const [result] = useQuery({
query: `
query GetMyBoards {
boards {
id
name
role # Your role on this board
}
}
`,
});
if (result.fetching) return <div>Loading...</div>;
if (result.error) return <div>Error: {result.error.message}</div>;
return (
<ul>
{result.data.boards.map((board) => (
<li key={board.id}>
{board.name} (Role: {board.role})
</li>
))}
</ul>
);
}
Error Handling
Handle auth errors gracefully:
import { useAuth } from "@weirdfingers/boards";
import { useEffect, useState } from "react";
function SignInForm() {
const { signIn } = useAuth();
const [error, setError] = useState<string | null>(null);
const handleSignIn = async (email: string, password: string) => {
try {
setError(null);
await signIn({ email, password });
} catch (err) {
setError(err.message || "Sign in failed");
}
};
return (
<form
onSubmit={(e) => {
e.preventDefault();
const formData = new FormData(e.target);
handleSignIn(formData.get("email"), formData.get("password"));
}}
>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign In</button>
{error && <div className="error">{error}</div>}
</form>
);
}
SSR/SSG Support (Next.js)
The auth system works with server-side rendering:
// pages/_app.tsx
import { AuthProvider, createGraphQLClient } from "@weirdfingers/boards";
import { Provider } from "urql";
import { useMemo } from "react";
function MyApp({ Component, pageProps }) {
const authProvider = useMemo(() => createAuthProvider(), []);
const graphqlClient = useMemo(
() =>
createGraphQLClient({
url: process.env.NEXT_PUBLIC_API_URL + "/graphql",
auth: authProvider,
tenantId: process.env.NEXT_PUBLIC_TENANT_ID,
}),
[authProvider]
);
return (
<AuthProvider provider={authProvider}>
<Provider value={graphqlClient}>
<Component {...pageProps} />
</Provider>
</AuthProvider>
);
}
Protected Routes
Create protected route components:
import { useAuth } from "@weirdfingers/boards";
import { useRouter } from "next/router";
import { useEffect } from "react";
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { status } = useAuth();
const router = useRouter();
useEffect(() => {
if (status === "unauthenticated") {
router.push("/login");
}
}, [status, router]);
if (status === "loading") {
return <div>Loading...</div>;
}
if (status === "unauthenticated") {
return null; // Redirecting...
}
return <>{children}</>;
}
// Usage
function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}
Environment Variables
Configure your auth provider with environment variables:
# Next.js (.env.local)
NEXT_PUBLIC_API_URL=http://localhost:8088
NEXT_PUBLIC_TENANT_ID=my-company
# For Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
# For Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
# For Auth0
NEXT_PUBLIC_AUTH0_DOMAIN=your-tenant.auth0.com
NEXT_PUBLIC_AUTH0_CLIENT_ID=your-client-id
Provider-Specific Setup
Each auth provider may have additional setup requirements:
- No Auth - Development setup
- JWT - Self-managed tokens
- Supabase - Supabase setup
- Clerk - Coming soon
- Auth0 - Coming soon
Troubleshooting
Common Issues
-
Token not included in requests: Make sure you're using the GraphQL client created with
createGraphQLClient() -
"Loading" state never resolves: Check that your auth provider is properly configured and can reach the backend
-
CORS errors: Ensure your backend allows requests from your frontend domain
-
Redirects not working in development: Some providers require HTTPS even in development
Debug Mode
Enable debug logging:
const authProvider = new JWTAuthProvider({
// ... config
debug: true, // Enable debug logs
});
Network Inspection
Check the Network tab in browser DevTools:
- Authentication requests should include
Authorization: Bearer <token>header - GraphQL requests should include
X-Tenant: <tenantId>header - Token refresh should happen automatically before expiry