bknd-login-flow
Use when implementing login and logout functionality in a Bknd application. Covers SDK authentication methods, REST API endpoints, React integration, session checking, and error handling.
What this skill does
# Login/Logout Flow
Implement user authentication flow with login, logout, and session checking in Bknd.
## Prerequisites
- Bknd project with auth enabled (`bknd-setup-auth`)
- At least one auth strategy configured (password, OAuth)
- For SDK: `bknd` package installed
- For React: `@bknd/react` package installed
## When to Use UI Mode
- Testing login/logout endpoints via admin panel
- Viewing active sessions
- Checking user authentication status
**UI steps:** Admin Panel > Auth > Test endpoints
## When to Use Code Mode
- Implementing login forms in your frontend
- Adding logout functionality
- Checking authentication state
- Building protected routes
- Handling authentication errors
## SDK Approach
### Initialize API Client
```typescript
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
storage: localStorage, // Persist token between sessions
});
```
**SDK options:**
| Option | Type | Description |
|--------|------|-------------|
| `host` | string | Backend URL |
| `storage` | Storage | Token persistence (localStorage, sessionStorage) |
| `token` | string | Pre-set auth token |
| `tokenTransport` | `"header"` \| `"cookie"` | How token is sent (default: header) |
### Login with Password Strategy
```typescript
async function login(email: string, password: string) {
const { ok, data, error, status } = await api.auth.login("password", {
email,
password,
});
if (ok) {
// Token automatically stored in localStorage
console.log("Logged in as:", data.user.email);
console.log("User ID:", data.user.id);
console.log("Role:", data.user.role);
return data.user;
}
// Handle errors
if (status === 401) {
throw new Error("Invalid email or password");
}
if (status === 403) {
throw new Error("Account uses different login method");
}
throw new Error(error?.message || "Login failed");
}
```
**Login response:**
```typescript
type LoginResponse = {
ok: boolean;
status: number;
data?: {
user: {
id: number | string;
email: string;
role?: string;
// Custom fields...
};
token: string;
};
error?: { message: string };
};
```
### Check Current User
```typescript
async function getCurrentUser() {
const { ok, data } = await api.auth.me();
if (ok && data?.user) {
return data.user; // User is authenticated
}
return null; // Not authenticated
}
```
### Logout
```typescript
async function logout() {
await api.auth.logout();
// Token removed from storage
// User is now logged out
}
```
### Check If Authenticated
```typescript
async function isAuthenticated(): Promise<boolean> {
const { ok, data } = await api.auth.me();
return ok && data?.user !== null;
}
```
### Complete Login Flow Example
```typescript
import { Api } from "bknd";
class AuthService {
private api: Api;
constructor() {
this.api = new Api({
host: import.meta.env.VITE_API_URL || "http://localhost:7654",
storage: localStorage,
});
}
async login(email: string, password: string) {
const result = await this.api.auth.login("password", { email, password });
if (!result.ok) {
throw new AuthError(result.status, result.error?.message);
}
return result.data!.user;
}
async logout() {
await this.api.auth.logout();
}
async getUser() {
const { ok, data } = await this.api.auth.me();
return ok ? data?.user : null;
}
async isAuthenticated() {
const user = await this.getUser();
return user !== null;
}
}
class AuthError extends Error {
constructor(public status: number, message?: string) {
super(message || "Authentication failed");
this.name = "AuthError";
}
}
// Usage
const auth = new AuthService();
try {
const user = await auth.login("[email protected]", "password123");
console.log("Welcome,", user.email);
} catch (e) {
if (e instanceof AuthError && e.status === 401) {
console.error("Wrong credentials");
}
}
```
## REST API Approach
### Login via REST
```bash
# Login
curl -X POST http://localhost:7654/api/auth/password/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
```
**Response:**
```json
{
"user": {
"id": 1,
"email": "[email protected]",
"role": "user"
},
"token": "eyJhbGciOiJIUzI1NiIs..."
}
```
### Use Token in Requests
```bash
# Get current user
curl http://localhost:7654/api/auth/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
# Access protected data
curl http://localhost:7654/api/data/posts \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```
### Logout via REST
```bash
curl -X POST http://localhost:7654/api/auth/logout \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```
**Note:** Logout clears server-side cookie. For header-based auth, client must discard token.
### REST Endpoints Reference
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/auth/password/login` | Login with email/password |
| GET | `/api/auth/me` | Get current authenticated user |
| POST | `/api/auth/logout` | Log out (clear session) |
| GET | `/api/auth/strategies` | List available strategies |
## React Integration
### Using useAuth Hook
```tsx
import { BkndProvider, useAuth } from "@bknd/react";
function App() {
return (
<BkndProvider config={{ host: "http://localhost:7654" }}>
<AuthExample />
</BkndProvider>
);
}
function AuthExample() {
const { user, isLoading, login, logout } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <LoginForm onLogin={login} />;
}
return (
<div>
<p>Welcome, {user.email}!</p>
<button onClick={logout}>Logout</button>
</div>
);
}
```
### Login Form Component
```tsx
import { useState } from "react";
import { useAuth } from "@bknd/react";
function LoginForm() {
const { login } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
setIsSubmitting(true);
try {
await login("password", { email, password });
// Redirect or update UI - user state updates automatically
} catch (err) {
setError(err instanceof Error ? err.message : "Login failed");
} finally {
setIsSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</label>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Logging in..." : "Login"}
</button>
</form>
);
}
```
### Protected Route Pattern
```tsx
import { Navigate } from "react-router-dom";
import { useAuth } from "@bknd/react";
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" replace />;
}
return <>{children}</>;
}
// Usage with React Router
function AppRoutes() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
</Routes>
);
}
```
### Logout Button Component
```tsx
import { useAuth } from "@bknd/react";
import { useNavigaRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.