> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Ishaq74/concordia/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication System

> Complete guide to Concordia's authentication system powered by Better Auth

Concordia uses [Better Auth](https://better-auth.com) for authentication and session management, configured with email/password authentication, organizations, and advanced security features.

## Overview

The authentication system provides:

* Email/password authentication with validation
* Email verification for new accounts
* Password reset flow
* Session management with automatic refresh
* Rate limiting for security
* Organization support with invitations
* Admin impersonation capabilities
* Comprehensive audit logging

## Authentication Setup

The auth configuration is centralized in `src/lib/auth/auth.ts`. Two instances are created:

1. **API Instance** (async): Used by the Astro application
2. **CLI Instance** (sync): Used by command-line tools and migrations

### Core Configuration

```typescript theme={null}
// From auth.ts:53-133
const sharedConfig = {
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
    async signUpValidator({ email, password, username, name }) {
      validateUserInput({ email, password, username, name });
    },
    async sendResetPassword({ user, url }) {
      await smtp.send({
        to: user.email,
        subject: "Password reset - Réinitialisation de votre mot de passe",
        text: `Cliquez sur le lien suivant pour réinitialiser votre mot de passe : ${url}`,
        html: `<p>Cliquez sur le lien suivant pour réinitialiser votre mot de passe :</p><p><a href="${url}">${url}</a></p>`,
      });
    },
  },
  emailVerification: {
    async sendVerificationEmail({ user, url }) {
      await smtp.send({
        to: user.email,
        subject: "verify your email address - Vérifiez votre adresse email",
        text: `Cliquez sur le lien suivant pour vérifier votre adresse email : ${url}`,
        html: `<p>Cliquez sur le lien suivant pour vérifier votre adresse email :</p><p><a href="${url}">${url}</a></p>`,
      });
    },
    sendOnSignUp: true,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7,        // 7 days
    updateAge: 60 * 60 * 24,             // refresh daily
    cookieCache: {
      enabled: true,
      strategy: "compact",
      maxAge: 60 * 5,                    // 5 minutes
    },
    freshAge: 60 * 10,                   // 10 minutes
    absoluteTimeout: 60 * 60 * 24 * 7,   // 7 days max
  },
};
```

## Plugins

Concordia uses three Better Auth plugins to extend functionality.

### Username Plugin

Enables username-based authentication in addition to email:

```typescript theme={null}
import { username } from "better-auth/plugins/username";

plugins: [
  username(),
  // ...
]
```

**Features**:

* Allows users to sign in with username or email
* Usernames are validated during signup
* Unique constraint enforced at database level

### Organization Plugin

Provides multi-tenant organization support:

```typescript theme={null}
// From auth.ts:158-173
organization({
  ac,  // Access control from permissions.ts
  roles,  // Role definitions (owner, admin, member)
  allowUserToCreateOrganization: async () => true,
  async sendInvitationEmail(data) {
    if (process.env.SMTP_MOCK === '1' || process.env.NODE_ENV === 'test') {
      console.log('[MOCK SMTP] Invite', { to: data.email, orgId: data.organization.id });
      return;
    }
    await smtp.send({
      to: data.email,
      subject: `Invitation à rejoindre ${data.organization.name}`,
      text: `Cliquez ici pour rejoindre : ${process.env.BETTER_AUTH_URL}/invite/${data.id}`,
    });
  },
})
```

**Organization API** (from `auth.ts:318-334`):

```typescript theme={null}
instance.organizationApi = {
  create: (payload) => instance.api.createOrganization(payload),
  setActive: (payload) => instance.api.setActiveOrganization(payload),
  update: (payload) => instance.api.updateOrganization(payload),
  delete: (payload) => instance.api.deleteOrganization(payload),
  inviteMember: (payload) => instance.api.createInvitation(payload),
  updateMemberRole: (payload) => instance.api.updateMemberRole(payload),
  removeMember: (payload) => instance.api.removeMember(payload),
  leave: (payload) => instance.api.leaveOrganization(payload),
  list: (payload) => instance.api.listOrganizations(payload),
  getFull: (payload) => instance.api.getFullOrganization(payload),
  listMembers: (payload) => instance.api.listMembers(payload),
  listUserInvitations: (payload) => instance.api.listUserInvitations(payload),
};
```

### Admin Plugin

Enables administrative capabilities:

```typescript theme={null}
import { admin } from "better-auth/plugins";

plugins: [
  admin(),
  // ...
]
```

**Admin capabilities**:

* User impersonation for support
* Direct user management
* Access to all organization data

## Email Verification

Email verification is **required** for all new accounts.

### Verification Flow

<Steps>
  <Step title="User signs up">
    User submits email, password, and optional username/name
  </Step>

  <Step title="Account created">
    User account is created with `emailVerified: false`
  </Step>

  <Step title="Verification email sent">
    System sends verification email with unique token link
  </Step>

  <Step title="User clicks link">
    User clicks verification link in email
  </Step>

  <Step title="Email verified">
    Account is marked as verified, user can fully access the platform
  </Step>
</Steps>

**Email template** (from `auth.ts:78-90`):

```typescript theme={null}
emailVerification: {
  async sendVerificationEmail({ user, url }) {
    await smtp.send({
      to: user.email,
      subject: "verify your email address - Vérifiez votre adresse email",
      text: `Cliquez sur le lien suivant pour vérifier votre adresse email : ${url}`,
      html: `<p>Cliquez sur le lien suivant pour vérifier votre adresse email :</p><p><a href="${url}">${url}</a></p>`,
    });
  },
  sendOnSignUp: true,
}
```

## Password Reset

Users can reset their password via email.

### Reset Flow

<Steps>
  <Step title="Request reset">
    User enters email address on forgot password page
  </Step>

  <Step title="Reset email sent">
    System sends password reset email with secure token
  </Step>

  <Step title="User clicks link">
    User clicks reset link (valid for limited time)
  </Step>

  <Step title="Set new password">
    User enters and confirms new password
  </Step>

  <Step title="Password updated">
    Password is updated, user can log in with new credentials
  </Step>
</Steps>

**Reset email template** (from `auth.ts:65-76`):

```typescript theme={null}
async sendResetPassword({ user, url }) {
  await smtp.send({
    to: user.email,
    subject: "Password reset - Réinitialisation de votre mot de passe",
    text: `Cliquez sur le lien suivant pour réinitialiser votre mot de passe : ${url}`,
    html: `<p>Cliquez sur le lien suivant pour réinitialiser votre mot de passe :</p><p><a href="${url}">${url}</a></p>`,
  });
}
```

## Session Management

Sessions are managed with secure cookies and automatic refresh.

### Session Configuration

```typescript theme={null}
// From auth.ts:93-103
session: {
  expiresIn: 60 * 60 * 24 * 7,        // 7 days
  updateAge: 60 * 60 * 24,             // refresh daily
  cookieCache: {
    enabled: true,
    strategy: "compact",
    maxAge: 60 * 5,                    // cache for 5 minutes
  },
  freshAge: 60 * 10,                   // consider fresh for 10 minutes
  absoluteTimeout: 60 * 60 * 24 * 7,   // max 7 days
}
```

**Session lifecycle**:

* Sessions expire after 7 days of inactivity
* Session tokens are automatically refreshed every 24 hours
* Cookie cache reduces database queries
* Absolute timeout ensures re-authentication after 7 days

### Session Security

```typescript theme={null}
// From auth.ts:178-192
advanced: {
  useSecureCookies: true,
  disableOriginCheck: false,
  trustedOrigins: [process.env.BETTER_AUTH_URL],
  cookiePrefix: "astro_",
  crossSubDomainCookies: { enabled: false },
  ipAddress: {
    ipAddressHeaders: ["x-real-ip", "x-forwarded-for", "cf-connecting-ip"],
    disableIpTracking: false,
  },
}
```

**Security features**:

* Secure cookies (HTTPS only in production)
* Origin checking enabled
* Trusted origins whitelist
* IP tracking for audit logs
* Bearer token invalidation on logout

## Rate Limiting

Built-in rate limiting protects against brute force attacks.

```typescript theme={null}
// From auth.ts:105-127
rateLimit: {
  enabled: true,
  storage: "database",
  skipSuccessfulRequests: false,
  skipFailedRequests: false,
  windows: [
    {
      key: "global_ip",
      max: 100,
      window: 60 * 1000,  // 100 requests per minute
    },
    {
      key: "sign_in",
      max: 5,
      window: 15 * 60 * 1000,  // 5 attempts per 15 minutes
    },
    {
      key: "sign_up",
      max: 10,
      window: 60 * 60 * 1000,  // 10 signups per hour
    },
  ],
}
```

**Rate limit windows**:

* **Global**: 100 requests per minute per IP
* **Sign in**: 5 attempts per 15 minutes
* **Sign up**: 10 registrations per hour

Rate limit counters are stored in the database for persistence across restarts.

## Post-Signup Hooks

When a user signs up, several actions occur automatically:

```typescript theme={null}
// From auth.ts:196-266
databaseHooks: {
  user: {
    create: {
      after: async (user) => {
        const derivedUsername = user.username || 
          user.email.split("@")[0].replace(/[^a-zA-Z0-9-]/g, "-").slice(0, 30);
        
        // Create profile
        await db.profile.insert({
          id: randomUUID(),
          userId: user.id,
          username: derivedUsername,
          fullName: user.name || null,
          preferredLanguage: "fr",
        });
        
        // Create wallet
        await db.wallet.insert({
          id: randomUUID(),
          userId: user.id,
          balance: "0.00",
          currency: "EUR",
        });
        
        // Assign citizen role
        await db.userRole.insert({
          id: randomUUID(),
          userId: user.id,
          role: "citizen",
          grantedBy: null,
        });
        
        // Audit log
        await db.insert(auditLog).values({
          id: randomUUID(),
          action: "signup",
          userId: user.id,
          data: { email: user.email, username: derivedUsername },
        });
      },
    },
  },
}
```

<Accordion title="Automatic Profile Creation">
  Every new user gets a profile with:

  * Auto-generated username (from email if not provided)
  * Preferred language set to French ("fr")
  * Empty bio and optional full name
</Accordion>

<Accordion title="Wallet Initialization">
  Every user receives a digital wallet:

  * Starting balance: 0.00 EUR
  * Ready for transactions, bookings, and donations
</Accordion>

<Accordion title="Default Role Assignment">
  All users are automatically granted the **citizen** role:

  * Enables core platform features
  * Additional roles can be granted by admins
</Accordion>

<Accordion title="Audit Logging">
  Signup events are logged:

  * User ID and email recorded
  * Timestamp captured
  * Available for compliance and debugging
</Accordion>

## Audit Logging

All authentication events are logged to the `audit_log` table.

### Login Success

```typescript theme={null}
// From auth.ts:290-306
session: {
  create: {
    after: async (session, ctx) => {
      const ip = extractIP(ctx);
      const userAgent = ctx?.request?.headers?.get("user-agent");
      
      await db.insert(auditLog).values({
        id: randomUUID(),
        action: "login_success",
        userId: session.userId,
        ip,
        userAgent,
        data: { sessionId: session.id },
      });
    },
  },
}
```

### Login Failure

```typescript theme={null}
// From auth.ts:25-50
async function logAuthError(error, ctx, db) {
  const ip = extractIP(ctx);
  const userAgent = ctx?.request?.headers?.get("user-agent");
  let email = "unknown";
  
  try {
    const body = await ctx?.request?.json?.();
    email = body?.email || "unknown";
  } catch {}
  
  await db.insert(auditLog).values({
    id: randomUUID(),
    action: "login_failed",
    userId: null,
    targetId: email,
    ip,
    userAgent,
    data: { email, error: error.message },
  });
}
```

**Logged events**:

* `signup` - New user registration
* `login_success` - Successful authentication
* `login_failed` - Failed login attempt (with IP and user agent)

## Usage Examples

### Get Current Session

```typescript theme={null}
import { getAuth } from "@lib/auth/auth";

const auth = await getAuth();
const session = await auth.api.getSession({ headers: request.headers });

if (!session?.user?.id) {
  return new Response("Unauthorized", { status: 401 });
}
```

### Sign Up New User

```typescript theme={null}
const result = await auth.api.signUpEmail({
  body: {
    email: "user@example.com",
    password: "SecurePass123!",
    username: "johndoe",
    name: "John Doe",
  },
});
```

### Sign In

```typescript theme={null}
const session = await auth.api.signInEmail({
  body: {
    email: "user@example.com",
    password: "SecurePass123!",
  },
});
```

### Request Password Reset

```typescript theme={null}
await auth.api.forgotPassword({
  body: {
    email: "user@example.com",
  },
});
```

### Sign Out

```typescript theme={null}
await auth.api.signOut({
  headers: request.headers,
});
```

## Security Best Practices

<Steps>
  <Step title="Always verify email">
    Email verification is required - do not disable it in production.
  </Step>

  <Step title="Use strong passwords">
    Password validation ensures minimum complexity requirements.
  </Step>

  <Step title="Monitor rate limits">
    Check rate limit metrics to detect potential attacks.
  </Step>

  <Step title="Review audit logs">
    Regularly review `audit_log` for suspicious activity.
  </Step>

  <Step title="Secure cookies in production">
    Ensure `useSecureCookies: true` and HTTPS is enforced.
  </Step>
</Steps>

## Environment Variables

Required configuration:

```bash theme={null}
# Better Auth
BETTER_AUTH_URL=https://concordia-annecy.fr
BETTER_AUTH_SECRET=your-secret-key-min-32-chars

# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/concordia

# SMTP (for emails)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@concordia-annecy.fr
SMTP_PASS=smtp-password
SMTP_FROM=noreply@concordia-annecy.fr
```

## See Also

* [User Roles & Permissions](/guides/user-roles)
* [Internationalization](/guides/internationalization)
* [Better Auth Documentation](https://better-auth.com)
