# How the Authentication Process Works in OpenWork with better-auth Integration

> Discover how OpenWork leverages better-auth for secure authentication. Explore email, OAuth, SSO, and SCIM flows with Drizzle ORM and custom middleware.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-07-31

---

**OpenWork uses the better-auth library as its central authentication engine, configured in [`ee/apps/den-api/src/auth.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth.ts) with a Drizzle ORM adapter, organization-based access control, and custom middleware hooks to secure email, OAuth, SSO, and SCIM flows.**

The `different-ai/openwork` repository implements a sophisticated **authentication process** using the extensible **better-auth** framework. By centralizing configuration in a single TypeScript file and layering custom business logic through plugins and access control lists, OpenWork secures multi-tenant API endpoints while supporting Managed Control Plane (MCP) integrations.

## Initializing the Better-Auth Server

The authentication pipeline begins in [`ee/apps/den-api/src/auth.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth.ts) where the `betterAuth()` function is instantiated with a comprehensive configuration object (lines 78-84). This setup defines the `baseURL`, a cryptographic secret, trusted origins, and social providers. Critically, it initializes a **Drizzle adapter** that connects better-auth to OpenWork’s MySQL schema, allowing the library to persist users, sessions, and organizations in the application database.

```typescript
import { auth } from "./auth.ts";

export const server = createServer({
  middleware: [auth.middleware],
});

```

## Session Lifecycle and Organization Context

Session management is customized through the `session.create.before` hook (lines 252-267). OpenWork sets a configurable lifetime via `DEN_SESSION_EXPIRES_IN_SECONDS` and automatically refreshes sessions. During creation, the hook injects the user’s **active organization ID** directly into the session payload, ensuring that every authenticated request carries the necessary tenant context for downstream authorization checks.

## Organization-Based Access Control

Access control relies on the better-auth `organization` plugin wired to a custom ACL defined in [`ee/apps/den-api/src/organization-access.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/organization-access.ts). The `denOrganizationAccess` catalog maps organization roles—such as owner, admin, and member—to specific permissions. When protecting routes, the `auth.protect` method validates the session’s organization membership and checks the user’s role against the required action.

```typescript
import { auth } from "./auth.ts";
import { denOrganizationAccess } from "./organization-access.ts";

export const secureEndpoint = auth.protect(async (ctx) => {
  const { session } = ctx;
  const orgId = session.activeOrganizationId!;
  const member = await ctx.context.getMember({ organizationId: orgId, userId: session.user.id });
  if (!denOrganizationAccess.can(member.role, "security_configuration", "manage")) {
    throw new APIError("FORBIDDEN", { message: "Insufficient permissions" });
  }
  return { ok: true };
});

```

## MCP OAuth Integration

For Managed Control Plane authentication, the `oauthProvider` plugin is configured with custom token prefixes (`ow_mcp_at_` for access tokens and `ow_mcp_rt_` for refresh tokens). A specialized **post-login flow** forces users to select an organization before MCP scopes are granted. The `shouldRedirect` logic checks if requested scopes include the `mcp:` prefix and if the session lacks an `activeOrganizationId`, redirecting to the organization selection page when necessary (lines 380-406).

```typescript
auth.oauthProvider.postLogin = {
  page: `${env.betterAuthUrl}/mcp/select-organization`,
  shouldRedirect: async ({ session, scopes }) => {
    return scopes.some((s) => s.startsWith("mcp:")) && !session.activeOrganizationId;
  },
  consentReferenceId: async ({ session }) => session.activeOrganizationId,
};

```

## SCIM, SSO, and Email Authentication

Enterprise features are enabled through dedicated plugins. The **`scim`** and **`sso`** plugins (lines 418-424) allow Just-In-Time (JIT) organization provisioning and domain-verified single sign-on. Traditional authentication is handled by the **`emailAndPassword`** plugin (lines 426-438), which supports email verification, password reset flows, and automatic session revocation upon password changes.

## Middleware Enforcement and Rate Limiting

Business rules are enforced via `createAuthMiddleware` wrappers registered in the `hooks.before` and `hooks.after` configuration (lines 442-525). This middleware validates live MCP sessions, enforces invitation role constraints, and blocks disallowed Better-Auth mutations—such as organization deletion—through the `getRawBetterAuthMutationDenial` utility. Additionally, the configuration applies **rate limiting** to auth endpoints and tracks token usage with custom prefixes (lines 540-559).

## Summary

- OpenWork centralizes its **authentication process** in [`ee/apps/den-api/src/auth.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth.ts) using the `betterAuth()` constructor with a Drizzle MySQL adapter.
- The **session lifecycle** is customized via hooks to inject the active organization ID, enabling multi-tenant access control.
- Role-based permissions are enforced through a custom **ACL** (`denOrganizationAccess`) integrated with the better-auth organization plugin.
- **MCP OAuth flows** use custom token prefixes and a forced organization selection post-login step before issuing tokens.
- Enterprise features (**SCIM**, **SSO**, **email/password**) are implemented as better-auth plugins, with request-level middleware blocking unauthorized mutations and applying rate limits.

## Frequently Asked Questions

### How is the database adapter configured for better-auth in OpenWork?

According to the source code in [`ee/apps/den-api/src/auth.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/auth.ts), the authentication instance uses a **Drizzle adapter** pointing to the application's MySQL schema, instantiated within the `betterAuth({ ... })` configuration block at lines 78-84.

### What triggers the organization selection screen during MCP authentication?

The `oauthProvider` plugin defines a `postLogin` hook that checks if requested scopes include the `mcp:` prefix and if the session lacks an `activeOrganizationId`. If both conditions are true, the user is redirected to the organization selection page before tokens are issued.

### How does OpenWork restrict destructive operations on organizations?

A custom middleware layer created with `createAuthMiddleware` intercepts requests to block specific Better-Auth mutations—such as organization deletion—via the `getRawBetterAuthMutationDenial` utility, while also enforcing invitation role rules and enterprise authentication requirements.

### Which better-auth plugins enable enterprise user provisioning?

OpenWork adds the **`scim`** and **`sso`** plugins to the better-auth configuration, allowing Just-In-Time (JIT) organization provisioning and domain-verified single sign-on, alongside the **`emailAndPassword`** plugin for traditional credential management.