# Authentication API Reference

Complete guide to authentication flows, session management, and security in Izri.

## 📋 Overview

Izri uses **Better Auth** for authentication with GitHub OAuth as the primary authentication method.

**Authentication Methods**:

- ✅ GitHub OAuth (implemented)
- 📋 API Tokens (planned)
- 📋 Email/Password (planned)

**Session Management**:

- Cookie-based sessions
- 7-day expiration (default)
- Automatic renewal on activity
- Multi-device support

---

## 🔐 GitHub OAuth Flow

### 1. Initiate Sign-In

**Frontend**:

```typescript
import { authClient } from '@izri/auth/client'

// Redirect to GitHub
await authClient.signIn.social({
  provider: 'github',
  callbackURL: '/dashboard'
})
```

**What Happens**:

1. Client calls Better Auth sign-in endpoint
2. Better Auth generates OAuth state
3. User redirected to GitHub authorization page
4. User approves access

### 2. GitHub Callback

**URL**: `http://localhost:4000/api/auth/callback/github?code=...&state=...`

**What Happens**:

1. GitHub redirects back with authorization code
2. Better Auth exchanges code for access token
3. Better Auth fetches user profile from GitHub
4. User created or updated in database
5. Session created
6. Session cookie set
7. User redirected to callbackURL

**Database Changes**:

```sql
-- Insert or update user
INSERT INTO users (id, email, name, avatar, email_verified)
VALUES (...) ON CONFLICT (email) DO UPDATE ...

-- Create account link (GitHub)
INSERT INTO accounts (user_id, provider, provider_account_id, access_token, ...)
VALUES (...)

-- Create session
INSERT INTO sessions (id, user_id, expires_at)
VALUES (...)
```

### 3. Session Cookie

**Cookie Name**: `better-auth.session_token`

**Attributes**:

- `HttpOnly`: Prevents JavaScript access
- `Secure`: HTTPS only (production)
- `SameSite=Lax`: CSRF protection
- `Max-Age`: 7 days (604800 seconds)

**Example**:

```
Set-Cookie: better-auth.session_token=session_abc123xyz; 
  HttpOnly; Secure; SameSite=Lax; Max-Age=604800; Path=/
```

---

## 🔑 Session Management

### Get Current Session

**tRPC**:

```typescript
const { data } = await trpc.auth.getSession.useQuery()
console.log(data?.user) // User object or null
console.log(data?.isAuthenticated) // boolean
```

**REST**:

```bash
curl http://localhost:4000/api/auth/get-session \
  --cookie "better-auth.session_token=..."
```

**Response**:

```json
{
  "session": {
    "id": "session_abc",
    "userId": "user_xyz",
    "expiresAt": "2024-12-31T23:59:59.999Z"
  },
  "user": {
    "id": "user_xyz",
    "email": "user@example.com",
    "name": "John Doe",
    "avatar": "https://avatars.githubusercontent.com/...",
    "emailVerified": true
  }
}
```

---

### List All Sessions

View all active sessions for current user.

**tRPC**:

```typescript
const { data } = await trpc.auth.listSessions.useQuery()
console.log(data) // Array of sessions
```

**Response**:

```typescript
[
  {
    id: 'session_current',
    userId: 'user_xyz',
    expiresAt: Date,
    createdAt: Date,
    ipAddress: '192.168.1.1',
    userAgent: 'Chrome/120.0...'
  },
  {
    id: 'session_mobile',
    // ... other session
  }
]
```

---

### Revoke Session (Logout)

**Current Session**:

```typescript
const { mutate: logout } = trpc.auth.invalidateSession.useMutation({
  onSuccess: () => {
    window.location.href = '/login'
  }
})

logout()
```

**Specific Session**:

```typescript
const { mutate } = trpc.auth.revokeSession.useMutation()
mutate({ sessionToken: 'session_to_revoke' })
```

**All Other Sessions** (keep current):

```typescript
const { mutate } = trpc.auth.revokeOtherSessions.useMutation()
mutate()
```

---

## 👤 User Profile

### Get Profile

**tRPC**:

```typescript
const { data: profile } = await trpc.auth.getProfile.useQuery()
```

**Response**:

```typescript
{
  id: string
  email: string
  name: string
  avatar?: string
  emailVerified: boolean
  createdAt: Date
  updatedAt: Date
}
```

---

### Update Profile

**tRPC**:

```typescript
const { mutate } = trpc.auth.updateProfile.useMutation()

mutate({
  name: 'New Name',
  avatar: 'https://example.com/avatar.jpg'
})
```

**Updatable Fields**:

- `name` - Display name
- `avatar` - Avatar URL

**Non-updatable**:

- `id`, `email`, `emailVerified`, `createdAt`

---

## 🔒 Protected Routes

### Frontend Route Protection

**React Router Loader**:

```typescript
// app/routes/_authenticated.tsx
export async function loader() {
  const session = await trpc.auth.getSession.query()
  
  if (!session.isAuthenticated) {
    throw redirect('/login')
  }
  
  return { user: session.user }
}
```

**Component**:

```typescript
import { useAuth } from '@/hooks/useAuth'

export function ProtectedPage() {
  const { user, isLoading } = useAuth()
  
  if (isLoading) return <LoadingSpinner />
  if (!user) return <Navigate to="/login" />
  
  return <div>Welcome {user.name}</div>
}
```

---

### Backend Procedure Protection

**tRPC Context**:

```typescript
// packages/trpc/src/context.ts
export async function createContext({ req, res }: Context) {
  const session = await auth.api.getSession({ headers: req.headers })
  
  return {
    req,
    res,
    db,
    session: session?.session,
    user: session?.user,
    isAuthenticated: !!session?.session
  }
}
```

**Protected Procedure**:

```typescript
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
  if (!ctx.isAuthenticated || !ctx.user) {
    throw new TRPCError({
      code: 'UNAUTHORIZED',
      message: 'Not authenticated'
    })
  }
  
  return next({
    ctx: {
      ...ctx,
      user: ctx.user,
      session: ctx.session
    }
  })
})
```

**Usage**:

```typescript
export const projectsRouter = router({
  getProjects: protectedProcedure
    .input(z.object({ ... }))
    .query(async ({ ctx, input }) => {
      // ctx.user is guaranteed to exist
      const userId = ctx.user.id
      // ...
    })
})
```

---

## 🔑 API Token Authentication (Planned)

### Token Structure

**Format**:

```
izri_v1_k7n9p2q5r8t1u4v7w0x3y6z9_c4a8
```

**Components**:

- `izri_` - Prefix
- `v1` - Version
- `k7n9...` - Random 32 chars (secret)
- `c4a8` - Checksum (verification)

### Token Storage

**Database** (hashed):

```sql
INSERT INTO api_tokens (
  id,
  user_id,
  name,
  token_hash,  -- SHA-256 hash
  scopes,
  expires_at
) VALUES (...)
```

**Never store plain tokens** - only hashes.

### Token Verification

```typescript
import crypto from 'node:crypto'

function hashToken(token: string): string {
  return crypto.createHash('sha256').update(token).digest('hex')
}

async function verifyToken(token: string): Promise<User | null> {
  const hash = hashToken(token)
  
  const tokenRecord = await db
    .select()
    .from(apiTokens)
    .where(eq(apiTokens.tokenHash, hash))
    .limit(1)
  
  if (!tokenRecord.length) return null
  if (tokenRecord[0].expiresAt < new Date()) return null
  
  const user = await db
    .select()
    .from(users)
    .where(eq(users.id, tokenRecord[0].userId))
    .limit(1)
  
  return user[0] || null
}
```

### Usage in Requests

**Authorization Header**:

```bash
curl http://localhost:4000/api/v1/projects \
  -H "Authorization: Bearer izri_v1_..."
```

**Middleware**:

```typescript
app.use('/api/v1/*', async (c, next) => {
  const auth = c.req.header('Authorization')
  if (!auth?.startsWith('Bearer ')) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  
  const token = auth.slice(7)
  const user = await verifyToken(token)
  
  if (!user) {
    return c.json({ error: 'Invalid token' }, 401)
  }
  
  c.set('user', user)
  await next()
})
```

---

## 🛡️ Security Best Practices

### Session Security

**Cookie Attributes**:

```typescript
{
  httpOnly: true,      // No JavaScript access
  secure: true,        // HTTPS only
  sameSite: 'lax',     // CSRF protection
  maxAge: 7 * 24 * 60 * 60  // 7 days
}
```

**Session Rotation**:

- Sessions refreshed on every request
- Old session tokens invalidated
- Prevents session fixation attacks

**Session Timeout**:

- 7-day absolute timeout
- 30-minute idle timeout (planned)
- Automatic cleanup of expired sessions

---

### Token Security

**Generation**:

```typescript
import crypto from 'node:crypto'

function generateToken(): string {
  const random = crypto.randomBytes(24).toString('base64url')
  const checksum = crypto.createHash('sha256')
    .update(random)
    .digest('base64url')
    .slice(0, 4)
  
  return `izri_v1_${random}_${checksum}`
}
```

**Storage**:

- ✅ Store hashed tokens in database
- ❌ Never log tokens
- ❌ Never return tokens after creation
- ✅ Show tokens once on creation

**Rotation**:

- Rotate tokens periodically (90 days recommended)
- Revoke unused tokens
- Alert on suspicious activity

---

### CORS Configuration

**Development**:

```typescript
cors({
  origin: ['http://localhost:5173'],
  credentials: true
})
```

**Production**:

```typescript
cors({
  origin: [
    'https://yourdomain.com',
    'https://www.yourdomain.com'
  ],
  credentials: true,
  maxAge: 86400  // 24 hours
})
```

---

## 📊 Audit Logging

### Auth Audit Logs

Track authentication events for security monitoring.

**Events Logged**:

- `login` - Successful login
- `logout` - User logout
- `session_revoked` - Session revoked
- `token_created` - API token created
- `token_revoked` - API token revoked
- `failed_auth` - Failed authentication attempt

**Schema**:

```typescript
{
  id: string
  userId: string
  action: string
  ipAddress?: string
  userAgent?: string
  metadata?: Record<string, any>
  createdAt: Date
}
```

**Query Logs**:

```typescript
const { data: logs } = await trpc.auth.getAuditLogs.useQuery({
  limit: 50,
  offset: 0
})
```

**Example Log Entry**:

```json
{
  "id": "log_abc123",
  "userId": "user_xyz",
  "action": "login",
  "ipAddress": "192.168.1.1",
  "userAgent": "Mozilla/5.0...",
  "metadata": {
    "provider": "github",
    "success": true
  },
  "createdAt": "2024-01-01T00:00:00.000Z"
}
```

---

## 🚨 Error Handling

### Common Errors

**UNAUTHORIZED (401)**:

```json
{
  "code": "UNAUTHORIZED",
  "message": "Not authenticated"
}
```

**Causes**:

- No session cookie
- Expired session
- Invalid session token

**Solution**: Redirect to login

---

**FORBIDDEN (403)**:

```json
{
  "code": "FORBIDDEN",
  "message": "Insufficient permissions"
}
```

**Causes**:

- Valid session but insufficient role
- API token with limited scopes

**Solution**: Request higher permissions or different token

---

**SESSION_EXPIRED**:

```json
{
  "code": "SESSION_EXPIRED",
  "message": "Your session has expired. Please log in again."
}
```

**Frontend Handling**:

```typescript
trpc.useContext().client.setDefaultOptions({
  onError: (error) => {
    if (error.data?.code === 'UNAUTHORIZED') {
      // Clear local state
      queryClient.clear()
      
      // Redirect to login
      window.location.href = '/login'
    }
  }
})
```

---

## 🔗 Related Documentation

- [tRPC Routers](./trpc-routers.md) - API endpoints using auth
- [REST Endpoints](./rest-endpoints.md) - REST auth endpoints
- [Error Codes](./error-codes.md) - Complete error reference
- [Auth Package](../packages/auth-package.md) - Implementation details
- [Backend Authentication](../backend/authentication.md) - Server-side auth
