bkend-auth

SkillCommunication

Gives your agent expert guidance on setting up app logins, sessions, roles, and password handling.

Use bkend-auth in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add bkend-auth and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the bkend-auth skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

bkend-authStart free
About this skill

bkend.ai authentication and security expert skill. Covers email signup/login, social login (Google, GitHub), magic link, JWT tokens (Access 1h, Refresh 30d), session management, RBAC (admin/user/self/guest), RLS policies, password management, and account lifecycle.

What this skill tells your AI

The instructions your AI receives, as published by ww-w-ai/bkit-gemini in skills/bkend-auth/SKILL.md and read by ahel’s review.

bkend.ai authentication and security expert skill

1. Auth Overview

bkend.ai uses JWT-based authentication with a dual-token strategy:

TokenTypeLifetimePurpose
Access TokenJWT1 hourAPI request authorization
Refresh TokenOpaque30 daysObtain new access tokens

Supported Authentication Methods

  1. Email/Password -- traditional signup and login
  2. Magic Link -- passwordless email-based login
  3. Social Login (OAuth) -- Google, GitHub
  4. API Key -- server-to-server (tenant-level, not user-level)

Required Headers

All auth endpoints require these headers:

X-Project-Id: <your-project-id>
X-Environment: <dev|staging|prod>
Content-Type: application/json

Authenticated endpoints additionally require:

Authorization: Bearer <access-token>

Auth Response Structure

All successful auth responses follow this pattern:

{
  "success": true,
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "name": "Alice",
      "role": "user",
      "emailVerified": true,
      "createdAt": "2025-01-15T09:00:00.000Z",
      "updatedAt": "2025-01-15T09:00:00.000Z"
    },
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "rt_a1b2c3d4e5f6...",
      "expiresIn": 3600
    }
  }
}

2. Email Authentication

2.1 Signup

Endpoint: POST /auth/email/signup

Request:

{
  "email": "user@example.com",
  "password": "SecureP@ss123",
  "name": "Alice Kim"
}

Response (201 Created):

{
  "success": true,
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "name": "Alice Kim",
      "role": "user",
      "emailVerified": false,
      "createdAt": "2025-01-15T09:00:00.000Z"
    },
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "rt_a1b2c3d4e5f6...",
      "expiresIn": 3600
    }
  }
}

Password Requirements:

  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • At least one special character

Error Responses:

HTTP StatusError CodeDescription
400INVALID_EMAILEmail format is invalid
400WEAK_PASSWORDPassword does not meet requirements
409EMAIL_ALREADY_EXISTSAccount with this email already exists
400MISSING_REQUIRED_FIELDRequired field (email, password) is missing

bkendFetch Example:

const result = await bkendFetch("/auth/email/signup", {
  method: "POST",
  body: JSON.stringify({
    email: "user@example.com",
    password: "SecureP@ss123",
    name: "Alice Kim",
  }),
});

// Store tokens
const { accessToken, refreshToken } = result.data.tokens;

2.2 Login

Endpoint: POST /auth/email/signin

Request:

{
  "email": "user@example.com",
  "password": "SecureP@ss123"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "name": "Alice Kim",
      "role": "user",
      "emailVerified": true,
      "lastLoginAt": "2025-01-20T14:30:00.000Z"
    },
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "rt_x9y8z7w6v5u4...",
      "expiresIn": 3600
    }
  }
}

Error Responses:

HTTP StatusError CodeDescription
401INVALID_CREDENTIALSEmail or password is incorrect
403ACCOUNT_DISABLEDAccount has been disabled
403ACCOUNT_LOCKEDToo many failed attempts (locked 30 min)
429TOO_MANY_ATTEMPTSRate limit exceeded

2.3 Email Verification

Send verification email:

POST /auth/email/verify/resend
{
  "email": "user@example.com"
}

Verify email with token:

POST /auth/email/verify
{
  "token": "ev_abc123def456..."
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Email verified successfully",
    "emailVerified": true
  }
}

3. Magic Link Authentication

Magic link provides passwordless authentication via email.

3.1 Send Magic Link

Endpoint: POST /auth/magiclink/send

Request:

{
  "email": "user@example.com",
  "redirectUri": "https://myapp.com/auth/callback"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Magic link sent to user@example.com",
    "expiresIn": 600
  }
}

The user receives an email with a link like:

https://api-client.bkend.ai/auth/magiclink/verify?token=ml_abc123...&redirectUri=https://myapp.com/auth/callback

3.2 Verify Magic Link

Endpoint: GET /auth/magiclink/verify?token=<token>&redirectUri=<uri>

The server verifies the token and redirects to redirectUri with tokens as query parameters:

https://myapp.com/auth/callback?accessToken=eyJ...&refreshToken=rt_...&expiresIn=3600

Client-side handling:

// app/auth/callback/page.tsx
"use client";

import { useSearchParams, useRouter } from "next/navigation";
import { useEffect } from "react";

export default function AuthCallback() {
  const searchParams = useSearchParams();
  const router = useRouter();

  useEffect(() => {
    const accessToken = searchParams.get("accessToken");
    const refreshToken = searchParams.get("refreshToken");

    if (accessToken && refreshToken) {
      // Store tokens securely
      document.cookie = `bkend_access_token=${accessToken}; path=/; secure; samesite=lax; max-age=3600`;
      document.cookie = `bkend_refresh_token=${refreshToken}; path=/; secure; samesite=lax; max-age=2592000`;
      router.push("/dashboard");
    } else {
      router.push("/login?error=invalid_magic_link");
    }
  }, [searchParams, router]);

  return <div>Authenticating...</div>;
}

Error Responses:

HTTP StatusError CodeDescription
400INVALID_MAGIC_LINKToken is invalid or malformed
410MAGIC_LINK_EXPIREDToken has expired (10 min lifetime)
400MAGIC_LINK_USEDToken has already been used

4. Social Login (OAuth)

4.1 Google OAuth

Console Configuration:

  1. Go to Console > Project > Settings > Auth > Social Login
  2. Enable Google provider
  3. Enter your Google Client ID and Client Secret
  4. Set authorized redirect URI: https://api-client.bkend.ai/auth/social/google/callback

Initiate Google Login:

GET /auth/social/google?redirectUri=https://myapp.com/auth/callback

The server redirects the user to Google's OAuth consent screen. After authorization, the user is redirected back to your redirectUri with tokens:

https://myapp.com/auth/callback?accessToken=eyJ...&refreshToken=rt_...&expiresIn=3600

bkendFetch Example (redirect):

function handleGoogleLogin() {
  const projectId = process.env.NEXT_PUBLIC_BKEND_PROJECT_ID;
  const env = process.env.NEXT_PUBLIC_BKEND_ENVIRONMENT;
  const redirectUri = encodeURIComponent(`${window.location.origin}/auth/callback`);

  window.location.href =
    `${process.env.NEXT_PUBLIC_BKEND_API_URL}/auth/social/google` +
    `?redirectUri=${redirectUri}` +
    `&projectId=${projectId}` +
    `&environment=${env}`;
}

4.2 GitHub OAuth

Console Configuration:

  1. Go to Console > Project > Settings > Auth > Social Login
  2. Enable GitHub provider
  3. Enter your GitHub Client ID and Client Secret
  4. Set authorization callback URL: https://api-client.bkend.ai/auth/social/github/callback

Initiate GitHub Login:

GET /auth/social/github?redirectUri=https://myapp.com/auth/callback

The flow is identical to Google. The user is redirected to GitHub for authorization, then back to your app with tokens.

Error Responses (Social Login):

HTTP StatusError CodeDescription
400SOCIAL_AUTH_FAILEDOAuth provider returned an error
400SOCIAL_EMAIL_NOT_FOUNDProvider did not return an email
409EMAIL_ALREADY_EXISTSEmail is linked to another auth method
400SOCIAL_PROVIDER_DISABLEDProvider not enabled in project settings

5. Token Management

5.1 Refresh Token

Endpoint: POST /auth/token/refresh

Request:

{
  "refreshToken": "rt_a1b2c3d4e5f6..."
}

Response (200 OK):

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "rt_newtoken123...",
    "expiresIn": 3600
  }
}

Error Responses:

HTTP StatusError CodeDescription
401INVALID_REFRESH_TOKENRefresh token is invalid
401REFRESH_TOKEN_EXPIREDRefresh token has expired (30 day lifetime)
401REFRESH_TOKEN_REVOKEDRefresh token has been revoked

5.2 Token Storage Patterns

Recommended: httpOnly Cookie (Server-rendered apps)

// API route: app/api/auth/login/route.ts
import { NextRequest, NextResponse } from "next/server";
import { bkendFetch } from "@/lib/bkend";

export async function POST(request: NextRequest) {
  const body = await request.json();

  const result = await bkendFetch("/auth/email/signin", {
    method: "POST",
    body: JSON.stringify(body),
  });

  const response = NextResponse.json({ user: result.data.user });

  response.cookies.set("bkend_access_token", result.data.tokens.accessToken, {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 3600, // 1 hour
  });

  response.cookies.set("bkend_refresh_token", result.data.tokens.refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "lax",
    path: "/",
    maxAge: 2592000, // 30 days
  });

  return response;
}

Alternative: Memory + localStorage (SPA)

// lib/auth-store.ts
class AuthStore {
  private accessToken: string | null = null;

  setTokens(accessToken: string, refreshToken: string) {
    this.accessToken = accessToken;
    localStorage.setItem("bkend_refresh_token", refreshToken);
  }

  getAccessToken(): string | null {
    return this.accessToken;
  }

  getRefreshToken(): string | null {
    return localStorage.getItem("bkend_refresh_token");
  }

  clearTokens() {
    this.accessToken = null;
    localStorage.removeItem("bkend_refresh_token");
  }
}

export const authStore = new AuthStore();

5.3 Auto-refresh Pattern (Next.js Middleware)

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

const PUBLIC_PATHS = ["/login", "/signup", "/", "/auth/callback"];

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (PUBLIC_PATHS.some((p) => pathname.startsWith(p))) {
    return NextResponse.next();
  }

  const accessToken = request.cookies.get("bkend_access_token")?.value;
  const refreshToken = request.cookies.get("bkend_refresh_token")?.value;

  // No tokens at all -- redirect to login
  if (!accessToken && !refreshToken) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  // Access token exists -- proceed
  if (accessToken) {
    return NextResponse.next();
  }

  // Access token expired, refresh token exists -- auto-refresh
  try {
    const res = await fetch(
      `${process.env.NEXT_PUBLIC_BKEND_API_URL}/auth/token/refresh`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Project-Id": process.env.NEXT_PUBLIC_BKEND_PROJECT_ID!,
          "X-Environment": process.env.NEXT_PUBLIC_BKEND_ENVIRONMENT!,
        },
        body: JSON.stringify({ refreshToken }),
      }
    );

    if (!res.ok) {
      throw new Error("Refresh failed");
    }

    const data = await res.json();
    const response = NextResponse.next();

    response.cookies.set("bkend_access_token", data.data.accessToken, {
      httpOnly: true,
      secure: true,
      sameSite: "lax",
      path: "/",
      maxAge: 3600,
    });

    if (data.data.refreshToken) {
      response.cookies.set("bkend_refresh_token", data.data.refreshToken, {
        httpOnly: true,
        secure: true,
        sameSite: "lax",
        path: "/",
        maxAge: 2592000,
      });
    }

    return response;
  } catch {
    const response = NextResponse.redirect(new URL("/login", request.url));
    response.cookies.delete("bkend_access_token");
    response.cookies.delete("bkend_refresh_token");
    return response;
  }
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|api).*)"],
};

6. Session Management

6.1 Revoke Current Session

Endpoint: POST /auth/session/revoke

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Session revoked successfully"
  }
}

6.2 Revoke All Sessions

Endpoint: POST /auth/session/revoke-all

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "All sessions revoked",
    "revokedCount": 5
  }
}

6.3 List Active Sessions

Endpoint: GET /auth/session/list

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "sessions": [
      {
        "id": "ses_abc123",
        "device": "Chrome on macOS",
        "ip": "192.168.1.1",
        "lastActiveAt": "2025-01-20T14:30:00.000Z",
        "createdAt": "2025-01-15T09:00:00.000Z",
        "current": true
      },
      {
        "id": "ses_def456",
        "device": "Safari on iPhone",
        "ip": "10.0.0.1",
        "lastActiveAt": "2025-01-19T10:00:00.000Z",
        "createdAt": "2025-01-18T08:00:00.000Z",
        "current": false
      }
    ]
  }
}

bkendFetch Example:

// Logout from current device
async function logout(token: string) {
  await bkendFetch("/auth/session/revoke", {
    method: "POST",
    token,
  });

  // Clear local tokens
  document.cookie = "bkend_access_token=; max-age=0; path=/";
  document.cookie = "bkend_refresh_token=; max-age=0; path=/";
  window.location.href = "/login";
}

// Logout from all devices
async function logoutAll(token: string) {
  await bkendFetch("/auth/session/revoke-all", {
    method: "POST",
    token,
  });
}

7. Password Management

7.1 Forgot Password

Endpoint: POST /auth/password/forgot

Request:

{
  "email": "user@example.com",
  "redirectUri": "https://myapp.com/reset-password"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Password reset email sent",
    "expiresIn": 3600
  }
}

The user receives an email with a link:

https://myapp.com/reset-password?token=pr_abc123...

7.2 Reset Password

Endpoint: POST /auth/password/reset

Request:

{
  "token": "pr_abc123...",
  "newPassword": "NewSecureP@ss456"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Password reset successfully"
  }
}

7.3 Change Password (Authenticated)

Endpoint: PUT /auth/password/change

Headers:

Authorization: Bearer <access-token>

Request:

{
  "currentPassword": "SecureP@ss123",
  "newPassword": "NewSecureP@ss456"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Password changed successfully"
  }
}

Error Responses:

HTTP StatusError CodeDescription
400INVALID_RESET_TOKENReset token is invalid
410RESET_TOKEN_EXPIREDReset token has expired (1 hour)
401INCORRECT_PASSWORDCurrent password is incorrect
400WEAK_PASSWORDNew password does not meet requirements
400SAME_PASSWORDNew password must differ from current

8. Multi-Factor Authentication (MFA)

8.1 Setup MFA

Endpoint: POST /auth/mfa/setup

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "qrCodeUrl": "otpauth://totp/bkend:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=bkend",
    "backupCodes": [
      "abc123def456",
      "ghi789jkl012",
      "mno345pqr678",
      "stu901vwx234",
      "yza567bcd890"
    ]
  }
}

8.2 Verify MFA

Endpoint: POST /auth/mfa/verify

Request:

{
  "code": "123456"
}

This endpoint is used both to complete MFA setup (first verification) and during login when MFA is enabled.

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "MFA verified successfully",
    "mfaEnabled": true
  }
}

When MFA is enabled, login responses include an mfaRequired flag:

{
  "success": true,
  "data": {
    "mfaRequired": true,
    "mfaToken": "mfa_temp_abc123..."
  }
}

The client must then call /auth/mfa/verify with the mfaToken header and the TOTP code.

8.3 Disable MFA

Endpoint: POST /auth/mfa/disable

Headers:

Authorization: Bearer <access-token>

Request:

{
  "code": "123456"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "MFA disabled successfully",
    "mfaEnabled": false
  }
}

9. Account Linking

Link multiple auth methods to a single user account.

9.1 Link Provider

Endpoint: POST /auth/link/{provider}

Supported providers: google, github

Headers:

Authorization: Bearer <access-token>

The server redirects to the OAuth provider. After authorization, the provider is linked to the current user account.

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Google account linked successfully",
    "linkedProviders": ["email", "google"]
  }
}

9.2 Unlink Provider

Endpoint: DELETE /auth/link/{provider}

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "Google account unlinked",
    "linkedProviders": ["email"]
  }
}

Error Responses:

HTTP StatusError CodeDescription
400PROVIDER_NOT_LINKEDProvider is not linked to this account
400LAST_AUTH_METHODCannot unlink the only remaining auth method
409PROVIDER_ALREADY_LINKEDProvider is already linked to another account

10. Invitation System

Invite users to join your application with a predefined role.

10.1 Send Invitation

Endpoint: POST /auth/invite

Headers:

Authorization: Bearer <access-token>

Request:

{
  "email": "newuser@example.com",
  "role": "user",
  "redirectUri": "https://myapp.com/invite/accept",
  "metadata": {
    "teamId": "team_abc123",
    "welcomeMessage": "Welcome to our platform!"
  }
}

Response (201 Created):

{
  "success": true,
  "data": {
    "inviteId": "inv_abc123",
    "email": "newuser@example.com",
    "role": "user",
    "status": "pending",
    "expiresAt": "2025-01-22T09:00:00.000Z"
  }
}

10.2 Accept Invitation

Endpoint: POST /auth/invite/accept

Request:

{
  "token": "inv_token_abc123...",
  "name": "New User",
  "password": "SecureP@ss123"
}

Response (200 OK):

{
  "success": true,
  "data": {
    "user": {
      "id": "usr_xyz789",
      "email": "newuser@example.com",
      "name": "New User",
      "role": "user"
    },
    "tokens": {
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "refreshToken": "rt_newuser123...",
      "expiresIn": 3600
    }
  }
}

11. User Management

11.1 List Users (Admin)

Endpoint: GET /users

Query Parameters:

ParameterTypeDefaultDescription
pagenumber1Page number
limitnumber20Items per page (max 100)
sortstring-createdAtSort field (prefix - for descending)
rolestring--Filter by role
searchstring--Search by name or email

Headers:

Authorization: Bearer <admin-access-token>

Response (200 OK):

{
  "success": true,
  "data": [
    {
      "id": "usr_abc123",
      "email": "user@example.com",
      "name": "Alice Kim",
      "role": "user",
      "emailVerified": true,
      "createdAt": "2025-01-15T09:00:00.000Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 45
  }
}

11.2 Get User by ID (Admin)

Endpoint: GET /users/:id

Response (200 OK):

{
  "success": true,
  "data": {
    "id": "usr_abc123",
    "email": "user@example.com",
    "name": "Alice Kim",
    "role": "user",
    "emailVerified": true,
    "linkedProviders": ["email", "google"],
    "mfaEnabled": false,
    "lastLoginAt": "2025-01-20T14:30:00.000Z",
    "createdAt": "2025-01-15T09:00:00.000Z",
    "updatedAt": "2025-01-20T14:30:00.000Z"
  }
}

11.3 Update User (Admin)

Endpoint: PUT /users/:id

Request:

{
  "name": "Alice Kim (Updated)",
  "role": "admin",
  "metadata": {
    "department": "Engineering"
  }
}

Response (200 OK):

{
  "success": true,
  "data": {
    "id": "usr_abc123",
    "name": "Alice Kim (Updated)",
    "role": "admin",
    "updatedAt": "2025-01-21T10:00:00.000Z"
  }
}

11.4 Delete User (Admin)

Endpoint: DELETE /users/:id

Response (200 OK):

{
  "success": true,
  "data": {
    "message": "User deleted successfully",
    "deletedId": "usr_abc123"
  }
}

11.5 Get Current User Profile

Endpoint: GET /users/me

Headers:

Authorization: Bearer <access-token>

Response (200 OK):

{
  "success": true,
  "data": {
    "id": "usr_abc123",
    "email": "user@example.com",
    "name": "Alice Kim",
    "role": "user",
    "emailVerified": true,
    "linkedProviders": ["email", "google"],
    "mfaEnabled": true,
    "metadata": {},
    "createdAt": "2025-01-15T09:00:00.000Z",
    "updatedAt": "2025-01-20T14:30:00.000Z"
  }
}

11.6 Update Current User Profile

Endpoint: PUT /users/me

Headers:

Authorization: Bearer <access-token>

Request:

{
  "name": "Alice K.",
  "metadata": {
    "avatar": "https://example.com/avatar.jpg",
    "bio": "Full-stack developer"
  }
}

Response (200 OK):

{
  "success": true,
  "data": {
    "id": "usr_abc123",
    "name": "Alice K.",
    "metadata": {
      "avatar": "https://example.com/avatar.jpg",
      "bio": "Full-stack developer"
    },
    "updatedAt": "2025-01-21T11:00:00.000Z"
  }
}

12. Auth Form Patterns (React / Next.js)

12.1 LoginForm Component

// components/auth/LoginForm.tsx
"use client";

import { useState, FormEvent } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/useAuth";

export function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const router = useRouter();
  const { login } = useAuth();

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    setError(null);
    setLoading(true);

    try {
      await login(email, password);
      router.push("/dashboard");
    } catch (err: any) {
      setError(err.message || "Login failed");
    } finally {
      setLoading(false);
    }
  }

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
66
Forks
16
Last commit
Sep 2026
Advanced
Item type
skill
Key
bkend-auth
Source
github.com/ww-w-ai/bkit-gemini