Bkend Auth logo

Bkend Auth

Organization
ww-w-ai
bkend-auth

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. Triggers: signup, login, JWT, session, social login, RBAC, RLS, password, token, 회원가입, 로그인, 토큰, 세션, 권한, 보안정책, 비밀번호, ログイン, 認証, セッション, 権限, パスワード, 登录, 认证, 会话, 权限, 密码, registro, inicio de sesion, permisos, contrasena, inscription, connexion, permissions, mot de passe, Registrierung, Anmeldung, Berechtigungen, Passwort, registrazione, accesso, permessi, password Do NOT use for: file storage (use bkend-storage), database queries (use bkend-data), MCP tool setup (use bkend-mcp)

Overview

Publisherww-w-ai
Repositorybkit-gemini
Skill namebkend-auth
Stars
66
Forks
16
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by ww-w-ai on GitHub. Read the source before you install it.

Installation

Install the Bkend Auth AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/ww-w-ai/bkit-gemini.git /tmp/bkit-gemini
mkdir -p .claude/skills
cp -r /tmp/bkit-gemini/skills/bkend-auth .claude/skills/bkend-auth
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Bkend Auth in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Bkend Auth on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Bkend Auth is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

bkend-auth

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:

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

Authenticated endpoints additionally require:

http
Authorization: Bearer <access-token>

Auth Response Structure

All successful auth responses follow this pattern:

json
{
  "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:

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

Response (201 Created):

json
{
  "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:

typescript
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:

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

Response (200 OK):

json
{
  "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
json
{
  "email": "user@example.com"
}

Verify email with token:

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

Response (200 OK):

json
{
  "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:

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

Response (200 OK):

json
{
  "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:

typescript
// 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):

typescript
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:

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

Response (200 OK):

json
{
  "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)

typescript
// 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)

typescript
// 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)

typescript
// 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:

http
Authorization: Bearer <access-token>

Response (200 OK):

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

6.2 Revoke All Sessions

Endpoint: POST /auth/session/revoke-all

Headers:

http
Authorization: Bearer <access-token>

Response (200 OK):

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

6.3 List Active Sessions

Endpoint: GET /auth/session/list

Headers:

http
Authorization: Bearer <access-token>

Response (200 OK):

json
{
  "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:

typescript
// 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:

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

Response (200 OK):

json
{
  "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:

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

Response (200 OK):

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

7.3 Change Password (Authenticated)

Endpoint: PUT /auth/password/change

Headers:

http
Authorization: Bearer <access-token>

Request:

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

Response (200 OK):

json
{
  "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:

http
Authorization: Bearer <access-token>

Response (200 OK):

json
{
  "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:

json
{
  "code": "123456"
}

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

Response (200 OK):

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

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

json
{
  "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:

http
Authorization: Bearer <access-token>

Request:

json
{
  "code": "123456"
}

Response (200 OK):

json
{
  "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:

http
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):

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

9.2 Unlink Provider

Endpoint: DELETE /auth/link/{provider}

Headers:

http
Authorization: Bearer <access-token>

Response (200 OK):

json
{
  "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:

http
Authorization: Bearer <access-token>

Request:

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

Response (201 Created):

json
{
  "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:

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

Response (200 OK):

json
{
  "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:

http
Authorization: Bearer <admin-access-token>

Response (200 OK):

json
{
  "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):

json
{
  "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:

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

Response (200 OK):

json
{
  "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):

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

11.5 Get Current User Profile

Endpoint: GET /users/me

Headers:

http
Authorization: Bearer <access-token>

Response (200 OK):

json
{
  "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:

http
Authorization: Bearer <access-token>

Request:

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

Response (200 OK):

json
{
  "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

typescript
// 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);
    }
  }

  function handleSocialLogin(provider: "google" | "github") {
    const baseUrl = process.env.NEXT_PUBLIC_BKEND_API_URL;
    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 =
      `${baseUrl}/auth/social/${provider}` +
      `?redirectUri=${redirectUri}` +
      `&projectId=${projectId}` +
      `&environment=${env}`;
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}

      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
      </div>

      <button type="submit" disabled={loading}>
        {loading ? "Signing in..." : "Sign In"}
      </button>

      <div className="social-login">
        <button type="button" onClick={() => handleSocialLogin("google")}>
          Continue with Google
        </button>
        <button type="button" onClick={() => handleSocialLogin("github")}>
          Continue with GitHub
        </button>
      </div>

      <div className="links">
        <a href="/forgot-password">Forgot password?</a>
        <a href="/signup">Create account</a>
      </div>
    </form>
  );
}

12.2 SignupForm Component

typescript
// components/auth/SignupForm.tsx
"use client";

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

export function SignupForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const router = useRouter();
  const { signup } = useAuth();

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

    if (password !== confirmPassword) {
      setError("Passwords do not match");
      return;
    }

    setLoading(true);

    try {
      await signup(email, password, name);
      router.push("/verify-email");
    } catch (err: any) {
      setError(err.message || "Signup failed");
    } finally {
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <div className="error">{error}</div>}

      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          required
        />
      </div>

      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
      </div>

      <div>
        <label htmlFor="password">Password</label>
        <input
          id="password"
          type="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
          minLength={8}
        />
      </div>

      <div>
        <label htmlFor="confirmPassword">Confirm Password</label>
        <input
          id="confirmPassword"
          type="password"
          value={confirmPassword}
          onChange={(e) => setConfirmPassword(e.target.value)}
          required
        />
      </div>

      <button type="submit" disabled={loading}>
        {loading ? "Creating account..." : "Create Account"}
      </button>

      <div className="links">
        <a href="/login">Already have an account? Sign in</a>
      </div>
    </form>
  );
}

12.3 AuthProvider Context

typescript
// providers/AuthProvider.tsx
"use client";

import {
  createContext,
  useContext,
  useState,
  useEffect,
  ReactNode,
} from "react";
import { bkendFetch } from "@/lib/bkend";

interface User {
  id: string;
  email: string;
  name: string;
  role: string;
  emailVerified: boolean;
}

interface AuthContextType {
  user: User | null;
  loading: boolean;
  login: (email: string, password: string) => Promise<void>;
  signup: (email: string, password: string, name: string) => Promise<void>;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
}

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

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

  useEffect(() => {
    refreshUser().finally(() => setLoading(false));
  }, []);

  async function refreshUser() {
    try {
      const result = await fetch("/api/auth/me");
      if (result.ok) {
        const data = await result.json();
        setUser(data.user);
      } else {
        setUser(null);
      }
    } catch {
      setUser(null);
    }
  }

  async function login(email: string, password: string) {
    const result = await fetch("/api/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!result.ok) {
      const error = await result.json();
      throw new Error(error.error?.message || "Login failed");
    }

    const data = await result.json();
    setUser(data.user);
  }

  async function signup(email: string, password: string, name: string) {
    const result = await fetch("/api/auth/signup", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password, name }),
    });

    if (!result.ok) {
      const error = await result.json();
      throw new Error(error.error?.message || "Signup failed");
    }

    const data = await result.json();
    setUser(data.user);
  }

  async function logout() {
    await fetch("/api/auth/logout", { method: "POST" });
    setUser(null);
    window.location.href = "/login";
  }

  return (
    <AuthContext.Provider
      value={{ user, loading, login, signup, logout, refreshUser }}
    >
      {children}
    </AuthContext.Provider>
  );
}

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

12.4 useAuth Hook

typescript
// hooks/useAuth.ts
"use client";

import { useAuthContext } from "@/providers/AuthProvider";

export function useAuth() {
  return useAuthContext();
}

12.5 Protected Route HOC

typescript
// components/auth/ProtectedRoute.tsx
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/useAuth";

interface ProtectedRouteProps {
  children: React.ReactNode;
  requiredRole?: string;
  fallback?: React.ReactNode;
}

export function ProtectedRoute({
  children,
  requiredRole,
  fallback,
}: ProtectedRouteProps) {
  const { user, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (!loading && !user) {
      router.push("/login");
    }
  }, [user, loading, router]);

  if (loading) {
    return fallback || <div>Loading...</div>;
  }

  if (!user) {
    return null;
  }

  if (requiredRole && user.role !== requiredRole && user.role !== "admin") {
    return <div>Access denied. Required role: {requiredRole}</div>;
  }

  return <>{children}</>;
}

// Usage:
// <ProtectedRoute requiredRole="admin">
//   <AdminDashboard />
// </ProtectedRoute>

12.6 RBAC Reference

bkend.ai supports four built-in roles:

RoleLevelDescriptionPermissions
admin4Full accessAll CRUD, user management, settings
user3Standard userCRUD own data, read shared data
self2Self-onlyRead/write only own records
guest1Read-onlyRead public data only

RLS (Row-Level Security) Policies:

Configure in Console > Project > Tables > [Table] > Security:

json
{
  "read": {
    "admin": "*",
    "user": "*",
    "self": { "createdBy": "{{userId}}" },
    "guest": { "isPublic": true }
  },
  "create": {
    "admin": "*",
    "user": "*",
    "self": "*",
    "guest": false
  },
  "update": {
    "admin": "*",
    "user": { "createdBy": "{{userId}}" },
    "self": { "createdBy": "{{userId}}" },
    "guest": false
  },
  "delete": {
    "admin": "*",
    "user": false,
    "self": { "createdBy": "{{userId}}" },
    "guest": false
  }
}

13. Error Codes Reference

Complete list of auth-related error codes:

Error CodeHTTP StatusDescription
INVALID_EMAIL400Email format is invalid
WEAK_PASSWORD400Password does not meet requirements
MISSING_REQUIRED_FIELD400A required field is missing
INVALID_REQUEST_BODY400Request body is malformed
EMAIL_ALREADY_EXISTS409An account with this email already exists
INVALID_CREDENTIALS401Email or password is incorrect
ACCOUNT_DISABLED403User account has been disabled by admin
ACCOUNT_LOCKED403Account locked due to too many failed attempts
EMAIL_NOT_VERIFIED403Email address has not been verified
INVALID_VERIFICATION_TOKEN400Email verification token is invalid
VERIFICATION_TOKEN_EXPIRED410Email verification token has expired
INVALID_MAGIC_LINK400Magic link token is invalid
MAGIC_LINK_EXPIRED410Magic link token has expired (10 min)
MAGIC_LINK_USED400Magic link has already been used
SOCIAL_AUTH_FAILED400OAuth provider returned an error
SOCIAL_EMAIL_NOT_FOUND400OAuth provider did not return an email
SOCIAL_PROVIDER_DISABLED400Social login provider not enabled
INVALID_ACCESS_TOKEN401Access token is invalid or malformed
ACCESS_TOKEN_EXPIRED401Access token has expired
INVALID_REFRESH_TOKEN401Refresh token is invalid
REFRESH_TOKEN_EXPIRED401Refresh token has expired (30 days)
REFRESH_TOKEN_REVOKED401Refresh token has been revoked
INVALID_RESET_TOKEN400Password reset token is invalid
RESET_TOKEN_EXPIRED410Password reset token has expired (1 hour)
INCORRECT_PASSWORD401Current password is incorrect
SAME_PASSWORD400New password must differ from current
MFA_REQUIRED403Multi-factor authentication is required
INVALID_MFA_CODE400MFA TOTP code is invalid
MFA_ALREADY_ENABLED409MFA is already enabled for this account
PROVIDER_NOT_LINKED400Auth provider is not linked to account
PROVIDER_ALREADY_LINKED409Provider already linked to another account
LAST_AUTH_METHOD400Cannot remove the last auth method
INVITE_NOT_FOUND404Invitation not found
INVITE_EXPIRED410Invitation has expired (7 days)
INVITE_ALREADY_ACCEPTED400Invitation has already been accepted
INSUFFICIENT_PERMISSIONS403User role lacks required permissions
USER_NOT_FOUND404User with given ID does not exist
TOO_MANY_ATTEMPTS429Rate limit exceeded, try again later
SESSION_NOT_FOUND404Session does not exist
SESSION_EXPIRED401Session has expired
MISSING_PROJECT_HEADER400X-Project-Id header is missing
MISSING_ENVIRONMENT_HEADER400X-Environment header is missing
INVALID_PROJECT_ID400Project ID is invalid or not found

Frequently asked questions

What does the Bkend Auth AI skill do?

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. Triggers: signup, login, JWT, session, social login, RBAC, RLS, password, token, 회원가입, 로그인, 토큰, 세션, 권한, 보안정책, 비밀번호, ログイン, 認証, セッション, 権限, パスワード, 登录, 认证, 会话, 权限, 密码, registro, inicio de sesion, permisos, contrasena, inscription, connexion, permissions, mot de passe, Registrierung, Anmeldung, Berechtigungen, Passwort, registrazione,...

Why use Bkend Auth on TypingMind?

Because you install it once and use it with any model. Bkend Auth is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Bkend Auth in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/ww-w-ai/bkit-gemini/tree/main/skills/bkend-auth. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Bkend Auth?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Bkend Auth?

As many as you like. As long as a model supports skills, you can use Bkend Auth with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Bkend Auth AI skill free?

Yes. It is published on GitHub by ww-w-ai under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇