# ACAI — Chapter 38: Authentication, Sessions, Authorization & Access Control

![Post cover](https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/06jjlbo0ls1skj4ywl2r.png align="center")

  

## 38.1 Introduction

Chapter 37 transformed the database architecture into a working PostgreSQL and Prisma implementation.

The next requirement is identity.

A database can store users, projects, files, conversations, and audit events, but the application must know:

*   Who is making a request?
    
*   Is that user authenticated?
    
*   Is the session still valid?
    
*   What resources does the user own?
    
*   What actions is the user permitted to perform?
    
*   Does the user have an administrator or ordinary-user role?
    
*   Should a particular operation require additional approval?
    

Therefore, authentication and authorization become a critical security boundary.

The fundamental architecture is:

```text
Client
  │
  ▼
Authentication
  │
  ▼
Session
  │
  ▼
Identity
  │
  ▼
Authorization
  │
  ▼
Resource Ownership
  │
  ▼
Business Operation
```

Authentication answers:

> "Who are you?"

Authorization answers:

> "What are you allowed to do?"

These are different security functions and should not be confused.

* * *

# 38.2 Authentication vs Authorization

Authentication verifies identity.

Examples:

```text
Email + Password
OAuth
Passkey
Magic Link
Multi-Factor Authentication
```

Authorization determines permissions.

Examples:

```text
User
Admin
Project Owner
Project Member
Reviewer
System Operator
```

A user can be successfully authenticated and still be unauthorized to access a resource.

For example:

```text
User A → authenticated
Project B → owned by User B
```

Authentication succeeds.

Authorization must reject the operation.

* * *

# 38.3 Security Model

The application should follow:

```text
REQUEST
   │
   ▼
Is request structurally valid?
   │
   ▼
Is user authenticated?
   │
   ▼
Does user have permission?
   │
   ▼
Does user own/access resource?
   │
   ▼
Is operation allowed?
   │
   ▼
Execute
```

A failure at any security boundary should stop the operation.

* * *

# 38.4 Extending the User Model

The user model can be extended with a role.

Example:

```prisma
enum UserRole {
  USER
  ADMIN
}

model User {
  id           String     @id @default(uuid())
  email        String     @unique
  name         String?
  status       UserStatus @default(ACTIVE)
  role         UserRole   @default(USER)

  createdAt    DateTime   @default(now())
  updatedAt    DateTime   @updatedAt
}
```

A migration is then created:

```bash
npx prisma migrate dev --name add_user_role
```

The migration should be reviewed before being applied to important environments.

* * *

# 38.5 Session Model

A production application needs a controlled session mechanism.

A conceptual session table can contain:

```text
Session
 ├── id
 ├── userId
 ├── tokenHash
 ├── expiresAt
 ├── createdAt
 ├── lastUsedAt
 └── revokedAt
```

Example Prisma model:

```prisma
model Session {
  id         String    @id @default(uuid())

  userId     String
  user       User      @relation(fields: [userId], references: [id], onDelete: Cascade)

  tokenHash  String    @unique

  expiresAt  DateTime
  lastUsedAt DateTime?
  revokedAt  DateTime?

  createdAt  DateTime  @default(now())

  @@index([userId])
  @@index([expiresAt])
  @@index([revokedAt])
}
```

The raw session token should not be stored unnecessarily.

A safer pattern is:

```text
Random session token
        │
        ▼
Hash
        │
        ▼
Database
```

The server can compare a presented token against the stored representation.

* * *

# 38.6 Session Lifecycle

A session follows:

```text
LOGIN
  │
  ▼
Credentials verified
  │
  ▼
Session created
  │
  ▼
Secure cookie issued
  │
  ▼
Authenticated requests
  │
  ▼
Session validation
  │
  ├── Valid → continue
  │
  └── Expired/revoked → reject
```

Logout:

```text
LOGOUT
  │
  ▼
Session revoked
  │
  ▼
Cookie removed
```

* * *

# 38.7 Password Storage

Passwords must never be stored as plaintext.

Bad:

```text
password = "mypassword123"
```

The database should instead contain a password hash.

Conceptually:

```text
Password
   │
   ▼
Password Hashing Function
   │
   ▼
Password Hash
   │
   ▼
Database
```

A modern password hashing algorithm designed for password storage should be used.

The application should never attempt to decrypt a password.

Password verification works by comparing a supplied password against the stored password hash.

* * *

# 38.8 Login Flow

A login request should conceptually follow:

```text
POST /api/auth/login
          │
          ▼
Validate input
          │
          ▼
Find user
          │
          ▼
Check account status
          │
          ▼
Verify password
          │
          ▼
Create session
          │
          ▼
Set secure cookie
          │
          ▼
Return safe user information
```

The response should not expose:

```text
passwordHash
sessionToken
private keys
internal database information
```

* * *

# 38.9 Secure Cookie Principles

For browser sessions, cookies should normally be configured with security properties such as:

```text
HttpOnly
Secure
SameSite
appropriate expiration
```

The purpose is to reduce unnecessary exposure of authentication material to client-side scripts and cross-site request scenarios.

The exact configuration should depend on the application's deployment architecture.

* * *

# 38.10 Current User Resolution

Create a server-side helper such as:

```text
src/lib/auth.ts
```

Conceptually:

```ts
export async function getCurrentUser() {
  // Read session information
  // Validate session
  // Load user
  // Return user or null
}
```

The helper becomes the central identity-resolution mechanism.

Example usage:

```ts
const user = await getCurrentUser();

if (!user) {
  throw new Error("Unauthorized");
}
```

The application should avoid duplicating authentication logic across every API route.

* * *

# 38.11 Authorization Helper

Authentication alone is insufficient.

A helper can enforce authorization:

```ts
export async function requireUser() {
  const user = await getCurrentUser();

  if (!user) {
    throw new Error("Unauthorized");
  }

  return user;
}
```

For administrative operations:

```ts
export async function requireAdmin() {
  const user = await requireUser();

  if (user.role !== "ADMIN") {
    throw new Error("Forbidden");
  }

  return user;
}
```

The distinction is:

```text
Unauthorized = identity not established

Forbidden = identity established but permission denied
```

* * *

# 38.12 Resource Ownership

Consider:

```text
GET /api/projects/abc123
```

The existence of project `abc123` does not automatically authorize access.

The server should evaluate:

```text
Current User
      │
      ▼
Project
      │
      ▼
Ownership / Membership
      │
      ▼
Permission
```

Example repository query:

```ts
const project = await prisma.project.findFirst({
  where: {
    id: projectId,
    ownerId: userId,
  },
});
```

If no record is returned, the server should not disclose whether a protected resource exists unless the application's policy explicitly permits such disclosure.

* * *

# 38.13 Ownership Check Pattern

A safe service operation might look like:

```ts
export async function getUserProject(
  userId: string,
  projectId: string
) {
  const project = await prisma.project.findFirst({
    where: {
      id: projectId,
      ownerId: userId,
    },
  });

  if (!project) {
    throw new Error("Project not found");
  }

  return project;
}
```

The important property is that the authorization condition is included in the database query itself.

* * *

# 38.14 Role-Based Access Control

The first role system can be simple:

```text
USER
ADMIN
```

Later it can become more granular:

```text
USER
PROJECT_MEMBER
PROJECT_OWNER
REVIEWER
ADMIN
SYSTEM_OPERATOR
```

However, roles should not be created merely because they sound useful.

Every role should have a clearly documented permission set.

Example:

| Role | View Own Data | Manage Own Projects | Manage Users |
| --- | --- | --- | --- |
| USER | Yes | Yes | No |
| ADMIN | Yes | Yes | Yes |

* * *

# 38.15 Permission-Based Design

For larger systems, permissions can be represented explicitly:

```text
project.read
project.create
project.update
project.delete

file.read
file.upload
file.delete

conversation.read
conversation.create

admin.users.read
admin.users.update
```

Then roles can map to permissions.

Conceptually:

```text
Role
  │
  ├── Permission A
  ├── Permission B
  └── Permission C
```

This is more flexible than embedding dozens of role-specific conditions throughout application code.

* * *

# 38.16 API Protection

Every protected API endpoint should have an explicit security boundary.

Example:

```ts
export async function GET() {
  const user = await requireUser();

  // Continue only after authentication.
}
```

For a project operation:

```ts
export async function PATCH(
  request: Request,
  context: {
    params: {
      id: string;
    };
  }
) {
  const user = await requireUser();

  const project =
    await getUserProject(
      user.id,
      context.params.id
    );

  // Authorized operation continues here.
}
```

* * *

# 38.17 Never Trust Client-Supplied Identity

A dangerous pattern is:

```json
{
  "userId": "some-id",
  "name": "new name"
}
```

followed by:

```ts
await updateUser(body.userId, ...)
```

The client should not be trusted to determine which authenticated identity is making the request.

Instead:

```text
Authenticated Session
       │
       ▼
Server determines userId
       │
       ▼
Operation
```

If the request body contains a user ID, it should not override the authenticated identity.

* * *

# 38.18 Authentication Rate Limiting

Login endpoints are security-sensitive.

A system should consider controls such as:

```text
Rate limiting
Credential attempt monitoring
Temporary throttling
Account protection
Suspicious activity detection
```

The objective is to reduce automated abuse without unnecessarily blocking legitimate users.

* * *

# 38.19 Session Expiration

Sessions should have expiration policies.

Example conceptual lifecycle:

```text
Session created
      │
      ▼
Active
      │
      ├── User logs out → Revoked
      │
      ├── Admin revokes → Revoked
      │
      └── Expiration reached → Expired
```

The server should validate expiration before granting access.

* * *

# 38.20 Session Revocation

A security-conscious system should be able to revoke sessions.

Examples:

```text
User logs out
Password changed
Account compromised
Administrator disables account
Suspicious session detected
```

The system can mark:

```text
revokedAt = current time
```

rather than relying only on client-side cookie removal.

* * *

# 38.21 Multiple Sessions

Users may have multiple active devices:

```text
Laptop
   │
   └── Session A

Phone
   │
   └── Session B

Tablet
   │
   └── Session C
```

A session-management interface can eventually allow the user to see and revoke individual sessions.

* * *

# 38.22 Account Status

Authentication should also respect account state.

For example:

```text
ACTIVE
SUSPENDED
DELETED
```

A suspended account may have valid credentials but should still be denied access.

Therefore:

```text
Valid credentials
        +
ACTIVE account
        +
Valid session
        =
Authenticated request
```

* * *

# 38.23 Admin Protection

Administrative functionality requires additional safeguards.

A basic model:

```text
Normal User
    ↓
Standard API

Admin
    ↓
Admin API
    ↓
Permission Check
    ↓
Audit Log
```

Administrative actions should generate audit events.

Example:

```text
ADMIN_USER_SUSPENDED
ADMIN_USER_RESTORED
ADMIN_PROJECT_REVIEWED
ADMIN_CONFIGURATION_CHANGED
```

* * *

# 38.24 Audit Integration

Authentication events should connect to the existing audit system.

Examples:

```text
LOGIN_SUCCESS
LOGIN_FAILURE
LOGOUT
SESSION_CREATED
SESSION_REVOKED
PASSWORD_CHANGED
ACCOUNT_SUSPENDED
ROLE_CHANGED
```

The audit system should avoid storing sensitive credentials.

* * *

# 38.25 Security Event Data

A useful audit record may include:

```text
userId
action
resource
resourceId
timestamp
request identifier
result
```

Potentially useful metadata:

```text
authentication method
application version
device category
```

Only collect information that is justified by the security and operational requirements.

* * *

# 38.26 Authentication Error Messages

Avoid revealing unnecessary account information.

For example, overly specific responses can disclose whether an account exists.

A safer external response can be intentionally generic:

```text
Authentication failed.
```

Detailed diagnostics remain server-side.

* * *

# 38.27 Database Constraints

Authentication security should not depend only on application code.

The database should enforce constraints such as:

```text
email UNIQUE
session token hash UNIQUE
foreign-key integrity
non-null required fields
```

Application validation and database constraints complement one another.

* * *

# 38.28 Security Testing

Authentication should be tested systematically.

Minimum test categories:

```text
Valid login
Invalid password
Unknown account
Expired session
Revoked session
Suspended account
Unauthenticated request
Unauthorized resource access
Admin-only operation
Logout
Multiple sessions
```

Example:

```text
Test:
User A requests User B's project.

Expected:
Access denied.
```

Another:

```text
Test:
Normal user calls admin endpoint.

Expected:
Forbidden.
```

* * *

# 38.29 Authorization Test Matrix

A useful matrix is:

| Operation | Anonymous | User | Owner | Admin |
| --- | --- | --- | --- | --- |
| Public landing page | ✓ | ✓ | ✓ | ✓ |
| Create own project | ✗ | ✓ | ✓ | ✓ |
| Read own project | ✗ | ✓ | ✓ | ✓ |
| Modify another user's project | ✗ | ✗ | ✗ | Policy-dependent |
| User administration | ✗ | ✗ | ✗ | ✓ |

The actual matrix must be defined by the application's requirements.

* * *

# 38.30 Defense-in-Depth

Authentication should not be the only security mechanism.

The system should combine:

```text
Input Validation
        +
Authentication
        +
Authorization
        +
Ownership Checks
        +
Database Constraints
        +
Rate Limiting
        +
Audit Logging
        +
Monitoring
```

If one control fails, other controls can still reduce the impact.

* * *

# 38.31 Zero-Trust Request Handling

A useful principle is:

> Every request should be treated as untrusted until the server validates it.

That means:

```text
Client-provided ID
Client-provided role
Client-provided ownership
Client-provided permission
```

should never be accepted as authoritative.

The server determines identity and authorization.

* * *

# 38.32 Authentication Architecture

The resulting architecture becomes:

```text
                   Request
                      │
                      ▼
               Input Validation
                      │
                      ▼
               Session Resolver
                      │
                      ▼
                Current User
                      │
                      ▼
              Authorization
                      │
          ┌───────────┴───────────┐
          │                       │
       Allowed                 Denied
          │                       │
          ▼                       ▼
    Service Layer              403/401
          │
          ▼
     Repository
          │
          ▼
       Prisma
          │
          ▼
     PostgreSQL
```

* * *

# 38.33 Recommended Authentication Boundaries

The application should have centralized functions for:

```text
getCurrentUser()
requireUser()
requireAdmin()
requireProjectOwner()
requirePermission()
createSession()
revokeSession()
```

Centralization reduces inconsistent security logic.

* * *

# 38.34 Future Authentication Extensions

The architecture can later support:

```text
Email verification
Password reset
Multi-factor authentication
Passkeys
OAuth providers
Device management
Session management
Security notifications
Recovery codes
Adaptive authentication
```

These should be introduced incrementally.

A simple, auditable authentication system is preferable to a complicated system that is difficult to verify.

* * *

# 38.35 Security Research Perspective

Authentication and authorization are particularly important in AI platforms because AI functionality often operates on valuable resources:

```text
Uploaded documents
Private conversations
Generated media
API usage
Model configurations
Stored memories
Agent tasks
Connected tools
```

A failure in access control can therefore expose more than ordinary profile information.

For an AI platform, authorization should be applied to the resources used by the AI system itself.

* * *

# 38.36 AI-Specific Authorization

An AI assistant should not automatically inherit unrestricted access to every resource belonging to a user.

Instead:

```text
User
 │
 ▼
Authorized Context
 │
 ├── Project A
 ├── Selected Files
 ├── Selected Conversations
 └── Approved Tools
```

The model receives only the context required for the task.

This creates an additional security boundary between:

```text
User permissions
```

and

```text
AI execution permissions
```

* * *

# 38.37 Agent Permission Boundary

For future agent functionality:

```text
Agent
 │
 ▼
Requested Action
 │
 ▼
Policy Check
 │
 ├── Allowed → Execute
 │
 └── Approval Required → Pause
```

The model should not be treated as an authority that can grant itself permissions.

Authorization belongs to the application.

* * *

# 38.38 Human Approval

Sensitive operations can use:

```text
REQUEST
  ↓
POLICY CHECK
  ↓
APPROVAL REQUIRED
  ↓
HUMAN REVIEW
  ↓
APPROVED / REJECTED
```

This becomes particularly useful for actions that have external side effects.

The approval decision should be auditable.

* * *

# 38.39 Chapter 38 Security Checklist

```text
[ ] Passwords are never stored in plaintext
[ ] Session data is protected
[ ] Sessions can expire
[ ] Sessions can be revoked
[ ] Cookies use appropriate security settings
[ ] Authentication is server-side
[ ] Authorization is server-side
[ ] Client-supplied user IDs are not trusted
[ ] Ownership is verified
[ ] Admin operations require authorization
[ ] Sensitive actions are audited
[ ] Account status is checked
[ ] Login abuse controls exist
[ ] Protected APIs require authentication
[ ] AI resources have authorization boundaries
[ ] Agent actions have explicit permissions
```

* * *

# 38.40 Conclusion

Chapter 38 established the identity and access-control layer.

The resulting security model is:

```text
Identity
   ↓
Session
   ↓
Authentication
   ↓
Authorization
   ↓
Ownership
   ↓
Permission
   ↓
Business Operation
```

This prevents a major architectural mistake: treating authentication as the entire security system.

Authentication establishes identity.

Authorization establishes permission.

Ownership establishes resource boundaries.

Audit logging establishes accountability.

Together, these form the foundation for a secure multi-user AI platform.

The next chapter will move into **API architecture and request processing**, including route organization, validation, response contracts, error handling, rate limiting, request IDs, service boundaries, and secure communication between the frontend and backend.

**END OF CHAPTER 38**
