Files
KohakuHub/docs/api/authentication.md
2025-10-24 18:45:55 +08:00

521 lines
9.9 KiB
Markdown

---
title: Authentication API
description: User registration, login, tokens, email verification, sessions
icon: i-carbon-password
---
# Authentication API
User authentication, registration, API token management, and email verification.
---
## Registration
### Register New User
**Pattern:** `POST /auth/register`
**Authentication:** Public (no auth required)
**Query Parameters:**
- `invitation_token` (optional): Required if server is in invitation-only mode
**Request Body:**
```json
{
"username": "alice",
"email": "alice@example.com",
"password": "secure_password_123"
}
```
**Response:**
```json
{
"success": true,
"message": "User created successfully",
"email_verified": true
}
```
**With Email Verification Enabled:**
```json
{
"success": true,
"message": "User created. Please check your email to verify your account.",
"email_verified": false
}
```
**Validation:**
- Username: Not reserved, not already taken, no normalized conflicts
- Email: Valid format, not already registered
- Password: Minimum length (enforced by server config)
**Reserved Usernames:**
`models`, `datasets`, `spaces`, `admin`, `api`, `organizations`, `settings`, `new`, `login`, `register`, `logout`, `docs`, and more (see validation.py)
**Status Codes:**
- `200 OK` - User created
- `400 Bad Request` - Username/email taken, reserved name, or validation error
- `403 Forbidden` - Invitation required but not provided
---
## Login & Sessions
### Login
**Pattern:** `POST /auth/login`
**Authentication:** Public
**Request Body:**
```json
{
"username": "alice",
"password": "secure_password_123"
}
```
**Response:**
```json
{
"success": true,
"message": "Logged in successfully",
"username": "alice",
"session_secret": "random_secret_for_client_side_encryption"
}
```
**Response Headers:**
```
Set-Cookie: session_id=abc123...; HttpOnly; SameSite=Lax; Max-Age=2592000
```
**Session Duration:** 30 days (default, configurable)
**Checks:**
- Username and password match
- Account is active (`is_active = true`)
- Email verified (if `require_email_verification = true`)
**Status Codes:**
- `200 OK` - Logged in successfully
- `401 Unauthorized` - Invalid credentials
- `403 Forbidden` - Account disabled or email not verified
---
### Logout
**Pattern:** `POST /auth/logout`
**Authentication:** Required (session cookie or token)
**Response:**
```json
{
"success": true,
"message": "Logged out successfully"
}
```
**Behavior:**
- Deletes all user sessions from database
- Clears session_id cookie
---
### Get Current User
**Pattern:** `GET /auth/me`
**Authentication:** Required
**Response:**
```json
{
"id": 1,
"username": "alice",
"email": "alice@example.com",
"email_verified": true,
"created_at": "2025-01-01T00:00:00Z"
}
```
**Use Case:** Check if still logged in, get basic user info
---
## API Tokens
### List Tokens
**Pattern:** `GET /auth/tokens`
**Authentication:** Required (session cookie or existing token)
**Response:**
```json
{
"tokens": [
{
"id": 1,
"name": "My API Token",
"last_used": "2025-01-20T10:15:32Z",
"created_at": "2025-01-01T00:00:00Z"
},
{
"id": 2,
"name": "CI/CD Token",
"last_used": null,
"created_at": "2025-01-15T08:00:00Z"
}
]
}
```
**Notes:**
- Token values are never shown after creation
- `last_used` updates when token is used
- Tokens don't expire by default
---
### Create Token
**Pattern:** `POST /auth/tokens/create`
**Authentication:** Required
**Request Body:**
```json
{
"name": "My API Token"
}
```
**Response:**
```json
{
"success": true,
"token": "hf_abc123def456ghi789jkl...",
"token_id": 3,
"session_secret": "encryption_secret",
"message": "Token created. Save it securely - you won't see it again!"
}
```
**Important:**
- Token value shown **only once**
- Copy immediately and store securely
- Cannot retrieve token value later
- Prefix: `hf_` (HuggingFace compatible)
---
### Revoke Token
**Pattern:** `DELETE /auth/tokens/{token_id}`
**Authentication:** Required (must own token)
**Response:**
```json
{
"success": true,
"message": "Token revoked successfully"
}
```
**Status Codes:**
- `200 OK` - Token revoked
- `404 Not Found` - Token not found or not owned by user
---
## Email Verification
### Verify Email
**Pattern:** `GET /auth/verify-email?token={verification_token}`
**Authentication:** Public
**Purpose:** Verify email address after registration
**Response:**
- Redirects to user profile on success: `302 -> /{username}`
- Redirects to login with error on failure: `302 -> /?error=invalid_token`
**Behavior:**
1. Validates verification token
2. Checks token not expired (24 hours default)
3. Marks user email as verified
4. Deletes verification token
5. Creates session and auto-logs in user
**Cookie Set:**
```
Set-Cookie: session_id=abc123...; HttpOnly; SameSite=Lax
```
---
### Resend Verification Email
**Pattern:** `POST /auth/resend-verification`
**Authentication:** Required (can be unverified user)
**Purpose:** Resend verification email if not received
**Response:**
```json
{
"success": true,
"message": "Verification email sent"
}
```
**Validation:**
- User must not be already verified
- Rate limit: Once per hour (TODO: implement)
---
## Usage Examples
### Register and Login
```python
import requests
API_BASE = "http://localhost:28080"
# Register
register_resp = requests.post(
f"{API_BASE}/auth/register",
json={
"username": "alice",
"email": "alice@example.com",
"password": "secure123"
}
)
result = register_resp.json()
print(result["message"])
# If email verification required
if not result.get("email_verified"):
print("Check your email and click verification link")
# After clicking link, user is auto-logged in
# Login (if email verification not required)
login_resp = requests.post(
f"{API_BASE}/auth/login",
json={
"username": "alice",
"password": "secure123"
}
)
# Session cookie set automatically
session_cookie = login_resp.cookies.get("session_id")
print(f"Session ID: {session_cookie}")
```
---
### Create and Use API Token
```python
import requests
API_BASE = "http://localhost:28080"
# Login first (or use existing session)
session = requests.Session()
session.post(
f"{API_BASE}/auth/login",
json={"username": "alice", "password": "secure123"}
)
# Create token
token_resp = session.post(
f"{API_BASE}/auth/tokens/create",
json={"name": "My API Token"}
)
token_data = token_resp.json()
token = token_data["token"] # hf_abc123...
print(f"Token created: {token}")
print("SAVE THIS TOKEN - you won't see it again!")
# Use token in API requests
headers = {"Authorization": f"Bearer {token}"}
# List repositories
repos_resp = requests.get(
f"{API_BASE}/api/models",
headers=headers
)
print(f"Found {len(repos_resp.json())} models")
```
---
### Manage Tokens
```python
# List all tokens
tokens_resp = session.get(f"{API_BASE}/auth/tokens")
tokens = tokens_resp.json()["tokens"]
for t in tokens:
print(f"Token: {t['name']} (ID: {t['id']})")
print(f" Created: {t['created_at']}")
print(f" Last used: {t['last_used'] or 'Never'}")
# Revoke old token
old_token_id = tokens[0]["id"]
revoke_resp = session.delete(f"{API_BASE}/auth/tokens/{old_token_id}")
print(revoke_resp.json()["message"])
```
---
### Registration with Invitation
```python
# For invitation-only servers
invitation_token = "invitation_token_from_link"
register_resp = requests.post(
f"{API_BASE}/auth/register?invitation_token={invitation_token}",
json={
"username": "bob",
"email": "bob@example.com",
"password": "secure456"
}
)
# If invitation was for joining an organization
# User automatically added to that organization
```
---
## Security
### Password Hashing
- Algorithm: bcrypt
- Salt rounds: Automatic (bcrypt default)
- Passwords never stored in plaintext
### Session Security
- HTTP-only cookies (not accessible via JavaScript)
- SameSite: Lax (CSRF protection)
- Secure flag in production (HTTPS only)
- 30-day expiration (configurable)
### Token Security
- SHA256 hashed in database
- Prefix: `hf_` (64 characters total)
- Never logged in plaintext
- Single-use viewing (on creation only)
### Session Secret
- Random 32-character string
- Used for client-side token encryption
- Unique per session
- Returned on login and token creation
---
## Configuration
### Environment Variables
```bash
# Session duration (hours)
KOHAKU_HUB_SESSION_EXPIRE_HOURS=720 # 30 days default
# Email verification
KOHAKU_HUB_REQUIRE_EMAIL_VERIFICATION=false
# Invitation-only registration
KOHAKU_HUB_INVITATION_ONLY=false
# Session secret (for cookie signing)
KOHAKU_HUB_SESSION_SECRET=change_this_in_production
# Email SMTP (for verification emails)
KOHAKU_HUB_SMTP_HOST=smtp.gmail.com
KOHAKU_HUB_SMTP_PORT=587
KOHAKU_HUB_SMTP_USERNAME=your_email@gmail.com
KOHAKU_HUB_SMTP_PASSWORD=your_app_password
KOHAKU_HUB_SMTP_FROM=noreply@yourdomain.com
```
---
## Best Practices
**For Users:**
- Use strong passwords (mix of letters, numbers, symbols)
- Create tokens for CI/CD (don't use password)
- Revoke unused tokens
- Don't share tokens in public repos
**For Developers:**
- Store tokens in environment variables
- Use session cookies for browser apps
- Use Bearer tokens for API/CLI
- Handle 401/403 errors (prompt re-login)
**For Admins:**
- Enable email verification in production
- Use invitation-only mode for private instances
- Set strong SESSION_SECRET
- Enable HTTPS in production
- Monitor token usage via admin API
---
## Error Responses
**401 Unauthorized:**
```json
{
"detail": "Invalid username or password"
}
```
**403 Forbidden:**
```json
{
"detail": "Account is disabled"
}
```
**400 Bad Request:**
```json
{
"detail": "Username already exists"
}
```
---
## Next Steps
- [User Settings API](./settings.md) - Profile, preferences
- [Organizations API](./organizations.md) - Org membership
- [Invitations API](./invitations.md) - Invite users
- [Admin API](./admin.md) - User management