# How Security is Handled in the ACE-Step UI Backend: JWT & Middleware Implementation

> Discover how ACE-Step UI backend secures your application with JWT and Express middleware. Learn about stateless authentication, SQLite user privileges, and database lookups for robust security.

- Repository: [fspecii/ace-step-ui](https://github.com/fspecii/ace-step-ui)
- Tags: how-to-guide
- Published: 2026-04-29

---

**The ACE-Step UI backend implements stateless authentication using JSON Web Tokens (JWT) verified through Express middleware, with user privileges stored in SQLite and enforced via database lookups.**

The ACE-Step UI project (`fspecii/ace-step-ui`) is designed as a local, single-user application that requires API protection without the complexity of enterprise identity providers. According to the source code, the backend handles security through a lightweight, three-layer validation system that combines cryptographically signed tokens, request middleware, and minimal database storage to enforce both authentication and authorization.

## JWT Configuration and Token Issuance

Security configuration is centralized in [`server/src/config/index.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/config/index.ts), where the JWT secret and expiration are defined. The system reads `JWT_SECRET` from environment variables, falling back to a development default, and sets tokens to expire after 365 days by default.

Token creation occurs in [`server/src/routes/auth.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/routes/auth.ts) via the `issueAccessToken` function. This method signs a payload containing the user's `id` and `username` using the configured secret:

```typescript
// server/src/routes/auth.ts
function issueAccessToken(payload: { id: string; username: string }): string {
  return jwt.sign(payload, config.jwt.secret, { expiresIn: config.jwt.expiresIn });
}

```

## Authentication Middleware Architecture

The backend provides three distinct middleware functions in [`server/src/middleware/auth.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/middleware/auth.ts) to handle different security requirements.

### Strict Token Validation with authMiddleware

The `authMiddleware` enforces mandatory authentication for protected endpoints. It inspects the `Authorization` header for a `Bearer` token, verifies it against the JWT secret, and attaches the decoded user object to the request. Invalid or missing tokens result in a **401** response:

```typescript
// server/src/middleware/auth.ts
export function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
  const token = authHeader.substring(7);
  try {
    const decoded = jwt.verify(token, config.jwt.secret);
    req.user = decoded;
    next();
  } catch {
    res.status(401).json({ error: 'Invalid or expired token' });
  }
}

```

### Optional Authentication Flows

For public endpoints that may still benefit from user context, `optionalAuthMiddleware` parses the token if present but never blocks the request. This allows the API to return personalized data for logged-in users while remaining accessible to anonymous traffic.

### Admin Privilege Enforcement

The `adminMiddleware` extends validation by querying the SQLite database after token verification. It checks the `users.is_admin` column to confirm elevated privileges, returning **403** for authenticated users who lack admin rights. This database-backed check ensures the JWT payload alone cannot grant administrative access.

## Securing API Routes

Route handlers apply these middleware layers declaratively. For example, song creation in [`server/src/routes/songs.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/routes/songs.ts) requires standard authentication:

```typescript
// server/src/routes/songs.ts
router.post('/', authMiddleware, async (req: AuthenticatedRequest, res) => {
  const userId = req.user!.id;
  // Song creation logic...
});

```

Conversely, administrative cleanup endpoints in [`server/src/routes/users.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/routes/users.ts) apply the stricter `adminMiddleware`:

```typescript
// server/src/routes/users.ts
router.delete('/admin/cleanup', adminMiddleware, async (req, res) => {
  // Administrative action logic...
});

```

## Input Sanitization and Database Security

Before persistence, user-supplied data undergoes sanitization. Usernames are trimmed, stripped of special characters (allowing only alphanumeric, underscore, and hyphen), and truncated to 50 characters:

```typescript
const sanitizedUsername = username
  .trim()
  .replace(/[^a-zA-Z0-9_-]/g, '')
  .slice(0, 50);

```

The SQLite database (`acestep.db`) stores only user IDs, usernames, and the `is_admin` boolean flag. Notably, the system does not persist user passwords, relying instead on the JWT's cryptographic integrity for session security.

## Environment Configuration and Secrets Management

Sensitive configuration resides in environment variables, with a `.env.example` file documenting required values without exposing real secrets. The JWT secret, database path, and token expiration are all configurable via environment variables, allowing production deployments to override development defaults.

## Frontend Integration Pattern

Client applications attach tokens to requests via the `Authorization` header. A typical service implementation retrieves the token from storage and includes it in fetch calls:

```typescript
// Frontend service example
export async function api<T>(endpoint: string, options: { token?: string } = {}): Promise<T> {
  const headers: HeadersInit = { 'Content-Type': 'application/json' };
  if (options.token) {
    headers['Authorization'] = `Bearer ${options.token}`;
  }
  
  const response = await fetch(`${API_BASE}${endpoint}`, {
    headers,
    credentials: 'include',
  });
  return response.json();
}

```

## Summary

- **Stateless JWT authentication** validates users via signed tokens stored in the `Authorization` header, implemented in [`server/src/middleware/auth.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/middleware/auth.ts).
- **Three middleware layers** provide strict authentication (`authMiddleware`), optional user context (`optionalAuthMiddleware`), and admin verification (`adminMiddleware`).
- **Database-backed authorization** confirms admin privileges by querying the `is_admin` column in SQLite, preventing privilege escalation through token manipulation.
- **Input sanitization** removes special characters and enforces length limits on usernames before database insertion.
- **Environment-driven secrets** allow customization of the JWT secret and expiration via `.env` configuration, with sensible defaults for local development.

## Frequently Asked Questions

### Does the ACE-Step UI backend store user passwords?

No. The SQLite database stores only the user ID, username, and `is_admin` flag. Authentication relies entirely on JWT tokens signed with a server-side secret, eliminating the need for password storage and the associated security risks.

### How long do authentication tokens remain valid?

By default, tokens expire after 365 days as configured in [`server/src/config/index.ts`](https://github.com/fspecii/ace-step-ui/blob/main/server/src/config/index.ts). You can override this duration by setting the `JWT_EXPIRES_IN` environment variable to adjust the token lifetime for your security requirements.

### How does the backend differentiate between regular users and administrators?

After JWT verification, the `adminMiddleware` queries the `users` table in SQLite to check the `is_admin` column. Only users with this flag set to `1` can access endpoints protected by this middleware, ensuring database-level authorization beyond the token payload.

### Can the JWT secret be customized for production deployments?

Yes. The secret is read from the `JWT_SECRET` environment variable, falling back to a hard-coded development value only when the variable is unset. Production deployments should always specify a cryptographically secure secret via the environment configuration documented in `.env.example`.