DEV Community

Cover image for ACAI — Chapter 38: Authentication, Sessions, Authorization & Access Control
Black Shadow Team ©
Black Shadow Team ©

Posted on

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

#ai

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:

Client
  │
  ▼
Authentication
  │
  ▼
Session
  │
  ▼
Identity
  │
  ▼
Authorization
  │
  ▼
Resource Ownership
  │
  ▼
Business Operation
Enter fullscreen mode Exit fullscreen mode

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:

Email + Password
OAuth
Passkey
Magic Link
Multi-Factor Authentication
Enter fullscreen mode Exit fullscreen mode

Authorization determines permissions.

Examples:

User
Admin
Project Owner
Project Member
Reviewer
System Operator
Enter fullscreen mode Exit fullscreen mode

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

For example:

User A → authenticated
Project B → owned by User B
Enter fullscreen mode Exit fullscreen mode

Authentication succeeds.

Authorization must reject the operation.


38.3 Security Model

The application should follow:

REQUEST
   │
   ▼
Is request structurally valid?
   │
   ▼
Is user authenticated?
   │
   ▼
Does user have permission?
   │
   ▼
Does user own/access resource?
   │
   ▼
Is operation allowed?
   │
   ▼
Execute
Enter fullscreen mode Exit fullscreen mode

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:

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
}
Enter fullscreen mode Exit fullscreen mode

A migration is then created:

npx prisma migrate dev --name add_user_role
Enter fullscreen mode Exit fullscreen mode

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:

Session
 ├── id
 ├── userId
 ├── tokenHash
 ├── expiresAt
 ├── createdAt
 ├── lastUsedAt
 └── revokedAt
Enter fullscreen mode Exit fullscreen mode

Example Prisma model:

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])
}
Enter fullscreen mode Exit fullscreen mode

The raw session token should not be stored unnecessarily.

A safer pattern is:

Random session token
        │
        ▼
Hash
        │
        ▼
Database
Enter fullscreen mode Exit fullscreen mode

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


38.6 Session Lifecycle

A session follows:

LOGIN
  │
  ▼
Credentials verified
  │
  ▼
Session created
  │
  ▼
Secure cookie issued
  │
  ▼
Authenticated requests
  │
  ▼
Session validation
  │
  ├── Valid → continue
  │
  └── Expired/revoked → reject
Enter fullscreen mode Exit fullscreen mode

Logout:

LOGOUT
  │
  ▼
Session revoked
  │
  ▼
Cookie removed
Enter fullscreen mode Exit fullscreen mode

38.7 Password Storage

Passwords must never be stored as plaintext.

Bad:

password = "mypassword123"
Enter fullscreen mode Exit fullscreen mode

The database should instead contain a password hash.

Conceptually:

Password
   │
   ▼
Password Hashing Function
   │
   ▼
Password Hash
   │
   ▼
Database
Enter fullscreen mode Exit fullscreen mode

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:

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

The response should not expose:

passwordHash
sessionToken
private keys
internal database information
Enter fullscreen mode Exit fullscreen mode

38.9 Secure Cookie Principles

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

HttpOnly
Secure
SameSite
appropriate expiration
Enter fullscreen mode Exit fullscreen mode

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:

src/lib/auth.ts
Enter fullscreen mode Exit fullscreen mode

Conceptually:

export async function getCurrentUser() {
  // Read session information
  // Validate session
  // Load user
  // Return user or null
}
Enter fullscreen mode Exit fullscreen mode

The helper becomes the central identity-resolution mechanism.

Example usage:

const user = await getCurrentUser();

if (!user) {
  throw new Error("Unauthorized");
}
Enter fullscreen mode Exit fullscreen mode

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


38.11 Authorization Helper

Authentication alone is insufficient.

A helper can enforce authorization:

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

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

  return user;
}
Enter fullscreen mode Exit fullscreen mode

For administrative operations:

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

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

  return user;
}
Enter fullscreen mode Exit fullscreen mode

The distinction is:

Unauthorized = identity not established

Forbidden = identity established but permission denied
Enter fullscreen mode Exit fullscreen mode

38.12 Resource Ownership

Consider:

GET /api/projects/abc123
Enter fullscreen mode Exit fullscreen mode

The existence of project abc123 does not automatically authorize access.

The server should evaluate:

Current User
      │
      ▼
Project
      │
      ▼
Ownership / Membership
      │
      ▼
Permission
Enter fullscreen mode Exit fullscreen mode

Example repository query:

const project = await prisma.project.findFirst({
  where: {
    id: projectId,
    ownerId: userId,
  },
});
Enter fullscreen mode Exit fullscreen mode

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:

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;
}
Enter fullscreen mode Exit fullscreen mode

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:

USER
ADMIN
Enter fullscreen mode Exit fullscreen mode

Later it can become more granular:

USER
PROJECT_MEMBER
PROJECT_OWNER
REVIEWER
ADMIN
SYSTEM_OPERATOR
Enter fullscreen mode Exit fullscreen mode

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:

project.read
project.create
project.update
project.delete

file.read
file.upload
file.delete

conversation.read
conversation.create

admin.users.read
admin.users.update
Enter fullscreen mode Exit fullscreen mode

Then roles can map to permissions.

Conceptually:

Role
  │
  ├── Permission A
  ├── Permission B
  └── Permission C
Enter fullscreen mode Exit fullscreen mode

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:

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

  // Continue only after authentication.
}
Enter fullscreen mode Exit fullscreen mode

For a project operation:

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.
}
Enter fullscreen mode Exit fullscreen mode

38.17 Never Trust Client-Supplied Identity

A dangerous pattern is:

{
  "userId": "some-id",
  "name": "new name"
}
Enter fullscreen mode Exit fullscreen mode

followed by:

await updateUser(body.userId, ...)
Enter fullscreen mode Exit fullscreen mode

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

Instead:

Authenticated Session
       │
       ▼
Server determines userId
       │
       ▼
Operation
Enter fullscreen mode Exit fullscreen mode

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:

Rate limiting
Credential attempt monitoring
Temporary throttling
Account protection
Suspicious activity detection
Enter fullscreen mode Exit fullscreen mode

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


38.19 Session Expiration

Sessions should have expiration policies.

Example conceptual lifecycle:

Session created
      │
      ▼
Active
      │
      ├── User logs out → Revoked
      │
      ├── Admin revokes → Revoked
      │
      └── Expiration reached → Expired
Enter fullscreen mode Exit fullscreen mode

The server should validate expiration before granting access.


38.20 Session Revocation

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

Examples:

User logs out
Password changed
Account compromised
Administrator disables account
Suspicious session detected
Enter fullscreen mode Exit fullscreen mode

The system can mark:

revokedAt = current time
Enter fullscreen mode Exit fullscreen mode

rather than relying only on client-side cookie removal.


38.21 Multiple Sessions

Users may have multiple active devices:

Laptop
   │
   └── Session A

Phone
   │
   └── Session B

Tablet
   │
   └── Session C
Enter fullscreen mode Exit fullscreen mode

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:

ACTIVE
SUSPENDED
DELETED
Enter fullscreen mode Exit fullscreen mode

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

Therefore:

Valid credentials
        +
ACTIVE account
        +
Valid session
        =
Authenticated request
Enter fullscreen mode Exit fullscreen mode

38.23 Admin Protection

Administrative functionality requires additional safeguards.

A basic model:

Normal User
    ↓
Standard API

Admin
    ↓
Admin API
    ↓
Permission Check
    ↓
Audit Log
Enter fullscreen mode Exit fullscreen mode

Administrative actions should generate audit events.

Example:

ADMIN_USER_SUSPENDED
ADMIN_USER_RESTORED
ADMIN_PROJECT_REVIEWED
ADMIN_CONFIGURATION_CHANGED
Enter fullscreen mode Exit fullscreen mode

38.24 Audit Integration

Authentication events should connect to the existing audit system.

Examples:

LOGIN_SUCCESS
LOGIN_FAILURE
LOGOUT
SESSION_CREATED
SESSION_REVOKED
PASSWORD_CHANGED
ACCOUNT_SUSPENDED
ROLE_CHANGED
Enter fullscreen mode Exit fullscreen mode

The audit system should avoid storing sensitive credentials.


38.25 Security Event Data

A useful audit record may include:

userId
action
resource
resourceId
timestamp
request identifier
result
Enter fullscreen mode Exit fullscreen mode

Potentially useful metadata:

authentication method
application version
device category
Enter fullscreen mode Exit fullscreen mode

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:

Authentication failed.
Enter fullscreen mode Exit fullscreen mode

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:

email UNIQUE
session token hash UNIQUE
foreign-key integrity
non-null required fields
Enter fullscreen mode Exit fullscreen mode

Application validation and database constraints complement one another.


38.28 Security Testing

Authentication should be tested systematically.

Minimum test categories:

Valid login
Invalid password
Unknown account
Expired session
Revoked session
Suspended account
Unauthenticated request
Unauthorized resource access
Admin-only operation
Logout
Multiple sessions
Enter fullscreen mode Exit fullscreen mode

Example:

Test:
User A requests User B's project.

Expected:
Access denied.
Enter fullscreen mode Exit fullscreen mode

Another:

Test:
Normal user calls admin endpoint.

Expected:
Forbidden.
Enter fullscreen mode Exit fullscreen mode

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:

Input Validation
        +
Authentication
        +
Authorization
        +
Ownership Checks
        +
Database Constraints
        +
Rate Limiting
        +
Audit Logging
        +
Monitoring
Enter fullscreen mode Exit fullscreen mode

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:

Client-provided ID
Client-provided role
Client-provided ownership
Client-provided permission
Enter fullscreen mode Exit fullscreen mode

should never be accepted as authoritative.

The server determines identity and authorization.


38.32 Authentication Architecture

The resulting architecture becomes:

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

38.33 Recommended Authentication Boundaries

The application should have centralized functions for:

getCurrentUser()
requireUser()
requireAdmin()
requireProjectOwner()
requirePermission()
createSession()
revokeSession()
Enter fullscreen mode Exit fullscreen mode

Centralization reduces inconsistent security logic.


38.34 Future Authentication Extensions

The architecture can later support:

Email verification
Password reset
Multi-factor authentication
Passkeys
OAuth providers
Device management
Session management
Security notifications
Recovery codes
Adaptive authentication
Enter fullscreen mode Exit fullscreen mode

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:

Uploaded documents
Private conversations
Generated media
API usage
Model configurations
Stored memories
Agent tasks
Connected tools
Enter fullscreen mode Exit fullscreen mode

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:

User
 │
 ▼
Authorized Context
 │
 ├── Project A
 ├── Selected Files
 ├── Selected Conversations
 └── Approved Tools
Enter fullscreen mode Exit fullscreen mode

The model receives only the context required for the task.

This creates an additional security boundary between:

User permissions
Enter fullscreen mode Exit fullscreen mode

and

AI execution permissions
Enter fullscreen mode Exit fullscreen mode

38.37 Agent Permission Boundary

For future agent functionality:

Agent
 │
 ▼
Requested Action
 │
 ▼
Policy Check
 │
 ├── Allowed → Execute
 │
 └── Approval Required → Pause
Enter fullscreen mode Exit fullscreen mode

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:

REQUEST
  ↓
POLICY CHECK
  ↓
APPROVAL REQUIRED
  ↓
HUMAN REVIEW
  ↓
APPROVED / REJECTED
Enter fullscreen mode Exit fullscreen mode

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

The approval decision should be auditable.


38.39 Chapter 38 Security Checklist

[ ] 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
Enter fullscreen mode Exit fullscreen mode

38.40 Conclusion

Chapter 38 established the identity and access-control layer.

The resulting security model is:

Identity
   ↓
Session
   ↓
Authentication
   ↓
Authorization
   ↓
Ownership
   ↓
Permission
   ↓
Business Operation
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)