Skip to content
bknd-login-flow logo

Login/Logout Flow

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.

SKILL.md

Full skill instructions

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

import { Api } from "bknd";

const api = new Api({
  host: "http://localhost:7654",
  storage: localStorage,  // Persist token between sessions
});

SDK options:

OptionTypeDescription
hoststringBackend URL
storageStorageToken persistence (localStorage, sessionStorage)
tokenstringPre-set auth token
tokenTransport"header" | "cookie"How token is sent (default: header)

Login with Password Strategy

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:

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

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

async function logout() {
  await api.auth.logout();
  // Token removed from storage
  // User is now logged out
}

Check If Authenticated

async function isAuthenticated(): Promise<boolean> {
  const { ok, data } = await api.auth.me();
  return ok && data?.user !== null;
}

Complete Login Flow Example

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

# Login
curl -X POST http://localhost:7654/​api/​auth/​password/​login \
  -H "Content-Type: application/​json" \
  -d '{"email": "[email protected]", "password": "password123"}'

Response:

{
  "user": {
    "id": 1,
    "email": "[email protected]",
    "role": "user"
  },
  "token": "eyJhbGciOiJIUzI1NiIs..."
}

Use Token in Requests

# 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

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

MethodPathDescription
POST/​api/​auth/​password/​loginLogin with email/​password
GET/​api/​auth/​meGet current authenticated user
POST/​api/​auth/​logoutLog out (clear session)
GET/​api/​auth/​strategiesList available strategies

React Integration

Using useAuth Hook

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

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

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

import { useAuth } from "@bknd/​react";
import { useNavigate } from "react-router-dom";

function LogoutButton() {
  const { logout } = useAuth();
  const navigate = useNavigate();

  async function handleLogout() {
    await logout();
    navigate("/​login");
  }

  return <button onClick={handleLogout}>Logout</​button>;
}

Manual API with React

If not using @bknd/​react:

import { useState, useEffect, createContext, useContext } from "react";
import { Api } from "bknd";

type User = { id: number; email: string; role?: string };

type AuthContextType = {
  user: User | null;
  isLoading: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => Promise<void>;
};

const AuthContext = createContext<AuthContextType | null>(null);

const api = new Api({
  host: "http://localhost:7654",
  storage: localStorage,
});

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  // Check auth on mount
  useEffect(() => {
    api.auth.me().then(({ ok, data }) => {
      setUser(ok ? data?.user ?? null : null);
      setIsLoading(false);
    });
  }, []);

  async function login(email: string, password: string) {
    const { ok, data, error } = await api.auth.login("password", {
      email,
      password,
    });

    if (!ok) {
      throw new Error(error?.message || "Login failed");
    }

    setUser(data!.user);
  }

  async function logout() {
    await api.auth.logout();
    setUser(null);
  }

  return (
    <AuthContext.Provider value={{ user, isLoading, login, logout }}>
      {children}
    </​AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error("useAuth must be used within AuthProvider");
  return context;
}

Cookie-Based Authentication

For browser apps with cookie transport:

const api = new Api({
  host: "http://localhost:7654",
  tokenTransport: "cookie",  // Use cookies instead of header
});

// Login sets httpOnly cookie automatically
await api.auth.login("password", { email, password });

// Subsequent requests include cookie automatically
await api.data.readMany("posts");

// Logout clears cookie
await api.auth.logout();

Cookie mode benefits:

  • HttpOnly cookies not accessible via JavaScript (XSS protection)
  • Automatic renewal on requests (if configured)
  • No manual token management

Error Handling

Error Codes

StatusMeaningCommon Cause
400Bad RequestMissing email/​password
401UnauthorizedInvalid credentials
403ForbiddenWrong strategy, disabled account
409ConflictAccount uses different login method

Handling Specific Errors

async function handleLogin(email: string, password: string) {
  const { ok, status, error } = await api.auth.login("password", {
    email,
    password,
  });

  if (ok) return;

  switch (status) {
    case 400:
      throw new Error("Please enter email and password");
    case 401:
      throw new Error("Invalid email or password");
    case 403:
      throw new Error("This account uses a different login method");
    case 409:
      throw new Error("Please use social login for this account");
    default:
      throw new Error(error?.message || "Login failed. Try again.");
  }
}

Common Patterns

Remember Me

Store token in localStorage (persistent) vs sessionStorage (per-session):

// Remember me enabled
const api = new Api({
  host: "http://localhost:7654",
  storage: localStorage,  // Persists across browser sessions
});

// Remember me disabled
const api = new Api({
  host: "http://localhost:7654",
  storage: sessionStorage,  // Cleared when tab closes
});

Auto-Refresh Token

If using short-lived tokens, refresh before expiry:

async function withAuth<T>(fn: () => Promise<T>): Promise<T> {
  try {
    return await fn();
  } catch (e) {
    // If 401, attempt refresh and retry
    if (e instanceof Error && e.message.includes("401")) {
      const { ok } = await api.auth.me();  // Refresh token
      if (ok) return await fn();
    }
    throw e;
  }
}

Redirect After Login

// Store intended destination
const redirectUrl = new URLSearchParams(window.location.search).get("redirect");

async function login(email: string, password: string) {
  await api.auth.login("password", { email, password });

  // Redirect to original destination or default
  window.location.href = redirectUrl || "/​dashboard";
}

Common Pitfalls

Token Not Persisted

Problem: User logged out after page refresh

Fix: Provide storage option to API client:

// Wrong - token lost on refresh
const api = new Api({ host: "http://localhost:7654" });

// Correct - token persisted
const api = new Api({
  host: "http://localhost:7654",
  storage: localStorage,
});

CORS Errors

Problem: Login fails with CORS error

Fix: Configure CORS on backend or use same origin:

// Backend config
{
  server: {
    cors: {
      origin: ["http://localhost:3000"],
      credentials: true,
    },
  },
}

Cookie Not Set (HTTPS)

Problem: Cookie not received in browser

Fix: Disable secure for local development:

// Backend config
{
  auth: {
    cookie: {
      secure: process.env.NODE_ENV === "production",
    },
  },
}

Multiple Strategy Conflict

Problem: User signed up with different strategy

Solution: Users can only have one strategy. If they registered with OAuth, they cannot use password login. Check user's strategy first or guide to correct login method.

Verification

Test login flow:

1. Login and get token:

curl -X POST http://localhost:7654/​api/​auth/​password/​login \
  -H "Content-Type: application/​json" \
  -d '{"email": "[email protected]", "password": "password123"}'

2. Use token to access protected endpoint:

curl http://localhost:7654/​api/​auth/​me \
  -H "Authorization: Bearer <token>"

3. Verify logout clears session:

curl -X POST http://localhost:7654/​api/​auth/​logout \
  -H "Authorization: Bearer <token>"

# Should now fail
curl http://localhost:7654/​api/​auth/​me \
  -H "Authorization: Bearer <token>"

DOs and DON'Ts

DO:

  • Store tokens securely (localStorage or httpOnly cookie)
  • Handle all error cases in login flow
  • Show loading state during auth operations
  • Redirect users after successful login
  • Clear auth state fully on logout

DON'T:

  • Store tokens in memory only (lost on refresh)
  • Ignore error handling for login
  • Expose tokens in URLs
  • Forget to handle expired tokens
  • Mix cookie and header auth without reason

Related Skills

  • bknd-setup-auth - Configure authentication system
  • bknd-registration - Set up user registration
  • bknd-session-handling - Manage user sessions
  • bknd-oauth-setup - OAuth/​social login providers
  • bknd-protect-endpoint - Secure specific endpoints

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View