Claude
Skills
Sign in
Back

bknd-login-flow

Included with Lifetime
$97 forever

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.

Web Dev

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 { useNaviga
Files: 1
Size: 15.0 KB
Complexity: 19/100
Category: Web Dev

Related in Web Dev