bkend-auth
SkillCommunicationGives 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.
Account requirements not reviewed. Check the skill instructions before use; ahel provides instructions and does not run this skill.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
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:
| Token | Type | Lifetime | Purpose |
|---|---|---|---|
| Access Token | JWT | 1 hour | API request authorization |
| Refresh Token | Opaque | 30 days | Obtain new access tokens |
Supported Authentication Methods
- Email/Password -- traditional signup and login
- Magic Link -- passwordless email-based login
- Social Login (OAuth) -- Google, GitHub
- 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 Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_EMAIL | Email format is invalid |
| 400 | WEAK_PASSWORD | Password does not meet requirements |
| 409 | EMAIL_ALREADY_EXISTS | Account with this email already exists |
| 400 | MISSING_REQUIRED_FIELD | Required 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 Status | Error Code | Description |
|---|---|---|
| 401 | INVALID_CREDENTIALS | Email or password is incorrect |
| 403 | ACCOUNT_DISABLED | Account has been disabled |
| 403 | ACCOUNT_LOCKED | Too many failed attempts (locked 30 min) |
| 429 | TOO_MANY_ATTEMPTS | Rate 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 Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_MAGIC_LINK | Token is invalid or malformed |
| 410 | MAGIC_LINK_EXPIRED | Token has expired (10 min lifetime) |
| 400 | MAGIC_LINK_USED | Token has already been used |
4. Social Login (OAuth)
4.1 Google OAuth
Console Configuration:
- Go to Console > Project > Settings > Auth > Social Login
- Enable Google provider
- Enter your Google Client ID and Client Secret
- 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:
- Go to Console > Project > Settings > Auth > Social Login
- Enable GitHub provider
- Enter your GitHub Client ID and Client Secret
- 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 Status | Error Code | Description |
|---|---|---|
| 400 | SOCIAL_AUTH_FAILED | OAuth provider returned an error |
| 400 | SOCIAL_EMAIL_NOT_FOUND | Provider did not return an email |
| 409 | EMAIL_ALREADY_EXISTS | Email is linked to another auth method |
| 400 | SOCIAL_PROVIDER_DISABLED | Provider 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 Status | Error Code | Description |
|---|---|---|
| 401 | INVALID_REFRESH_TOKEN | Refresh token is invalid |
| 401 | REFRESH_TOKEN_EXPIRED | Refresh token has expired (30 day lifetime) |
| 401 | REFRESH_TOKEN_REVOKED | Refresh 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 Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_RESET_TOKEN | Reset token is invalid |
| 410 | RESET_TOKEN_EXPIRED | Reset token has expired (1 hour) |
| 401 | INCORRECT_PASSWORD | Current password is incorrect |
| 400 | WEAK_PASSWORD | New password does not meet requirements |
| 400 | SAME_PASSWORD | New 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 Status | Error Code | Description |
|---|---|---|
| 400 | PROVIDER_NOT_LINKED | Provider is not linked to this account |
| 400 | LAST_AUTH_METHOD | Cannot unlink the only remaining auth method |
| 409 | PROVIDER_ALREADY_LINKED | Provider 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number |
limit | number | 20 | Items per page (max 100) |
sort | string | -createdAt | Sort field (prefix - for descending) |
role | string | -- | Filter by role |
search | string | -- | 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
github.com/ww-w-ai/bkit-gemini
More in Communication
Skill · anthropics
More in Communicationerror-handling
Skill · affaan-m
More in Communicationwait-what
Skill · mattpocock
More in Communicationemails
Skill · coreyhaines31
More in Communicationazure-messaging
Skill · microsoft
More in Communicationinvestor-outreach
Skill · affaan-m
More in Communication