alepha@docs:~/docs/guides/server$
cat 2-authentication.md
2 min read
Last commit:

#Authentication & Security

In most frameworks, adding authentication involves:

  1. Installing Passport.js / NextAuth.
  2. Configuring a session store (Redis).
  3. Writing middleware to check headers.
  4. Manually hashing passwords.
  5. Praying you didn't leave a hole.

In Alepha, authentication is built on two low-level primitives: $issuer and $auth. You can use them directly for full control, or use the higher-level presets for common scenarios.

#The Low-Level Primitives

#$issuer: Token Management

An Issuer is a JWT token provider. It handles JWT token creation, verification, and role management. It doesn't care how users authenticate—it just issues and validates tokens.

typescript
 1import { $issuer } from "alepha/security"; 2import { $env, t } from "alepha"; 3  4class AppSecurity { 5  env = $env(t.object({ 6    ISSUER_SECRET: t.string({ default: "***********" }), 7  })) 8  9  // Internal issuer: you control the secret, you can forge tokens10  internal = $issuer({11    name: "app",12    secret: this.env.ISSUER_SECRET,13    roles: [{14      name: "user",15      permissions: [{ name: "*" }],16    }],17    settings: {18      accessToken: { expiration: [15, "minutes"] },19      refreshToken: { expiration: [30, "days"] },20    }21  });22 23  // External issuer (delegation): validate tokens from Keycloak, Auth0, etc. You can't forge tokens here.24  external = $issuer({25    name: "keycloak",26    jwks: () => "https://auth.example.com/realms/myrealm/protocol/openid-connect/certs",27    roles: [{28      // Map role "user" to all permissions29      name: "user",30      permissions: [{ name: "*" }],31    }],32  });33}
  • Including an $issuer in your app automatically enables security check.
  • System is permission based by default. You can define roles and permissions as needed.
  • Alepha generates permissions automatically for each action (e.g., module:action).
  • $issuer is considered low-level. You usually want to use high-level $realm for full user management.

Use $issuer directly when:

  • You're integrating with an external identity provider (Keycloak, Auth0, Okta).
  • You need fine-grained control over token lifetimes and claims.
  • You're building a custom authentication flow.

#$auth: Login Flows

The $auth primitive handles how users authenticate. It supports multiple strategies:

typescript
 1import { $auth } from "alepha/server/auth"; 2  3class AuthProviders { 4  env = $env(t.object({ 5    GITHUB_CLIENT_ID: t.string(), 6    GITHUB_CLIENT_SECRET: t.string(), 7    KEYCLOAK_URL: t.string(), 8  })); 9 10  // 1. Credentials: username/password11  credentials = $auth({12    issuer: this.security.internal,13    credentials: {14      account: async ({ username, password }) => {15        // Your validation logic here16        return await this.validateUser(username, password);17      }18    }19  });20 21  // 2. OAuth2: external provider (manual config)22  github = $auth({23    issuer: this.security.internal,24    oauth: {25      clientId: this.env.GITHUB_CLIENT_ID,26      clientSecret: this.env.GITHUB_CLIENT_SECRET,27      authorization: "https://github.com/login/oauth/authorize",28      token: "https://github.com/login/oauth/access_token",29      scope: "user:email",30      userinfo: async (tokens) => {31        // Fetch user profile from GitHub API32        return await fetchGitHubUser(tokens.access_token);33      },34    }35  });36 37  // 3. OIDC: OpenID Connect (Keycloak, Auth0, Okta, etc.)38  keycloak = $auth({39    oidc: {40      issuer: `${this.env.KEYCLOAK_URL}/realms/customers`,41      clientId: "my-app",42    },43    fallback: () => generateAnonymousToken(), // Optional: return anonymous token if not authenticated44  });45}

The oidc strategy auto-discovers endpoints from the issuer's .well-known/openid-configuration. No manual URL wiring needed—just point it at your identity provider and go.

#The High-Level Way (Recommended)

For most SaaS applications, you don't want to wire all this yourself. Alepha provides $realm—an extension of $issuer that includes:

  • User accounts stored in your database
  • Password hashing (Scrypt)
  • Session management
  • Email verification hooks
typescript
 1import { $realm } from "alepha/api/users"; 2  3class AppSecurity { 4  realm = $realm({ 5    settings: { 6      registrationAllowed: true, 7      emailRequired: true, 8    }, 9    identities: {10      google: true,11    }12  });13}

#Auth Presets

Similarly, instead of configuring OAuth2 manually, use the presets:

typescript
 1import { $authGoogle, $authGithub, $authCredentials } from "alepha/server/auth"; 2  3class AuthProviders { 4  // Username/password with your $realm 5  credentials = $authCredentials(this.realm); 6  7  // Google OAuth2 (auto-configured) 8  google = $authGoogle(this.realm, { 9    clientId: "...",10    clientSecret: "...",11  });12 13  // GitHub OAuth2 (auto-configured)14  github = $authGithub(this.realm, {15    clientId: "...",16    clientSecret: "...",17  });18}

#Protecting Routes

To protect an endpoint, tell the $action who is allowed in.

  • $action: By default, only authenticated users can access.
  • $route: secure: true to require authentication.
typescript
 1class UserController { 2  // Only logged-in users can see this 3  getProfile = $action({ 4    path: "/me", 5    handler: async ({ user }) => { 6      return user; 7    } 8  }); 9 10  noAuthNeeded = $action({11    secure: false,12    handler: () => "This is public!",13  });14}

#Frontend Integration

On the client (React), you don't need to manage tokens manually. Alepha handles the cookies for you.

tsx
 1import { useAuth } from "@alepha/react/auth"; 2import type { AuthProviders } from "./AuthProviders"; 3  4const LoginPage = () => { 5  // Type parameter gives you autocomplete for provider names 6  const auth = useAuth<AuthProviders>(); 7  8  if (auth.user) { 9    return (10      <div>11        <p>Welcome, {auth.user.name}</p>12        <button onClick={() => auth.logout()}>Logout</button>13      </div>14    );15  }16 17  return (18    <div>19      {/* OAuth/OIDC: redirects to provider */}20      <button onClick={() => auth.login("keycloak")}>21        Sign in with Keycloak22      </button>23      <button onClick={() => auth.login("github")}>24        Sign in with GitHub25      </button>26 27      {/* Credentials: pass username/password directly */}28      <form onSubmit={(e) => {29        e.preventDefault();30        auth.login("credentials", {31          username: email,32          password: password,33        });34      }}>35        <input type="email" placeholder="Email" />36        <input type="password" placeholder="Password" />37        <button type="submit">Sign in</button>38      </form>39    </div>40  );41};

The generic type useAuth<AuthProviders>() gives you autocomplete for provider names—no more typos. OAuth providers redirect to the identity provider; credentials submit directly.

#Permission Checking

The can() helper checks if the current user has permission to call an action:

tsx
 1const AdminPanel = () => { 2  const auth = useAuth<AuthProviders>(); 3  4  // Check permission before showing UI 5  if (!auth.can("deleteUser")) { 6    return <p>Access denied</p>; 7  } 8  9  return <button>Delete User</button>;10};

That's it. No complex contexts, no interceptors. It just works.

On This Page
No headings found...
ready
mainTypeScript
UTF-8guides_server_authentication.md