Auth Module
The Auth module provides user authentication, group management, role-based access control, and a flexible permission system that integrates with all other Cohera modules.
Overview
Section titled “Overview”Built on better-auth, the Auth module extends its foundation with Cohera-specific plugins for groups, roles, and module-level permissions. This hybrid permission model combines:
- Groups: Context-specific collections (forums, communities, organizations)
- Roles: User positions within groups (admin, moderator, member, etc.)
- Permissions: Module-declared actions that roles can perform
Configuration
Section titled “Configuration”Configure the auth module in your cohera.config.js:
import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '$env/static/private'
export default { modules: { auth: { // Authentication providers providers: { credentials: { enabled: true, requireEmailVerification: true, }, oauth: { google: { clientId: GOOGLE_CLIENT_ID, clientSecret: GOOGLE_CLIENT_SECRET, }, github: { clientId: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET, }, }, },
// Multi-factor authentication mfa: { enabled: true, required: false, // Enforce for all users methods: ["totp", "sms"], },
// Session management session: { expiresIn: "7d", updateAge: "1d", cookieName: "cohera.session", },
// Default roles available in all groups (groups can override) defaultRoles: [ { name: "admin", permissions: ["*"], // All permissions }, { name: "moderator", permissions: [ "posts.delete", "posts.edit", "posts.moderate", "users.moderate", ], }, { name: "member", permissions: ["posts.create", "posts.edit.own"], }, ],
// Password requirements password: { minLength: 8, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSpecialChars: false, }, }, },};Configuration Schema
Section titled “Configuration Schema”providers
Section titled “providers”Authentication provider configuration.
- Type:
object - Required: Yes
providers.credentials
Section titled “providers.credentials”Email/password authentication settings.
- Type:
object enabled(boolean): Enable credential-based authrequireEmailVerification(boolean): Require email verification before login
providers.oauth
Section titled “providers.oauth”OAuth provider configurations. Each provider requires clientId and clientSecret.
All providers supported by better-auth can be provided and configured.
See google for an example.
- Type:
object
Multi-factor authentication settings.
- Type:
object enabled(boolean): Enable MFA supportrequired(boolean): Require MFA for all usersmethods(array): Available MFA methods -['totp', 'email']totp: a one time password generated by a separate authenticator app (for example a password manager).email: a one time code is sent to the users email.
session
Section titled “session”Session management configuration. This controls how long users stay logged in. Optional configuration with sane defaults.
- Type:
object expiresIn(string): Session lifetime (e.g.,'7d','24h')updateAge(string): How often to update session expirycookieName(string): Session cookie name
defaultRoles
Section titled “defaultRoles”Platform-wide roles automatically available in all groups. Groups can override these by defining a role with the same name.
- Type:
array<object> - Each role object:
name(string): Role identifierpermissions(array): Array of permission strings, or['*']for all permissions
password
Section titled “password”Password validation requirements.
- Type:
object minLength(number): Minimum password lengthrequireUppercase(boolean): Require uppercase lettersrequireLowercase(boolean): Require lowercase lettersrequireNumbers(boolean): Require numbersrequireSpecialChars(boolean): Require special characters
Groups, Roles & Permissions
Section titled “Groups, Roles & Permissions”Permission Model
Section titled “Permission Model”The Auth module uses a hybrid permission system where:
- Modules declare permissions in their config (e.g.,
posts.create,posts.moderate) - Roles grant permissions to users within specific groups
- Middleware validates permissions before allowing actions
Example: Forum Moderation
Section titled “Example: Forum Moderation”// User Alice in "Gaming" forum{ userId: "alice", groupId: "gaming-forum", role: ["moderator"] // Has posts.delete permission}
// Same user Alice in "Cooking" forum{ userId: "alice", groupId: "cooking-forum", roles: [] // empty roles == base member role}When Alice tries to delete a post, the middleware checks:
hasPermission(alice, post.groupId, "posts.delete");// → true in gaming-forum// → false in cooking-forumGroup Structure
Section titled “Group Structure”Groups represent context-specific collections where users have different roles:
interface Group { id: string; parent?: Group; name: string; slug: string; description?: string; visibility: "public" | "private" | "secret"; createdAt: Date; updateAt: Date;}Nested Groups
Section titled “Nested Groups”Groups can contain subgroups, enabling hierarchical structures like organizations, departments, and teams. The parent field in the Group interface references the parent group.
Key behaviors:
- Creator becomes admin: When creating a subgroup, the creator is automatically added as the initial member with the admin role
- No membership inheritance: Users in a parent group are NOT automatically members of subgroups - they must be explicitly added
- No role inheritance: A user’s role in a parent group does NOT carry over to subgroups
- Default roles apply: Platform-wide
defaultRolesare available in all groups unless explicitly overridden
Example: Organization Hierarchy
Section titled “Example: Organization Hierarchy”// Top-level organization{ id: "acme-corp", name: "Acme Corporation", slug: "acme", parent: undefined}
// Department subgroup{ id: "engineering", name: "Engineering", slug: "engineering", parent: { id: "acme-corp", ... }}
// Team subgroup{ id: "frontend-team", name: "Frontend Team", slug: "frontend", parent: { id: "engineering", ... }}In this example, a user who is an admin of “Acme Corporation” does NOT automatically have any role in “Engineering” or “Frontend Team”. They must be explicitly added to each subgroup where they need access.
Role Assignment
Section titled “Role Assignment”Users are assigned roles within specific groups:
interface GroupMembership { userId: string; groupId: string; role: string; // References role name from config joinedAt: Date;}Module Permission Declarations
Section titled “Module Permission Declarations”Modules declare required permissions in their config:
// posts module configexport default { permissions: [ { name: "posts.create", description: "Create new posts", }, { name: "posts.edit", description: "Edit any post", }, { name: "posts.edit.own", description: "Edit own posts", }, { name: "posts.delete", description: "Delete posts", }, { name: "posts.moderate", description: "Moderate posts (feature, pin, etc.)", }, ],};CLI Commands
Section titled “CLI Commands”cohera auth user create
Section titled “cohera auth user create”Create a new user account.
Usage:
cohera auth user create <email> [options]Flags:
--password <password>- Set initial password--role <role>- Assign global role--verified- Mark email as verified--admin- Create as admin user
Examples:
# Create user with interactive promptscohera auth user create alice@example.com
# Create verified admin usercohera auth user create admin@example.com --verified --admin
# Create user with specific passwordcohera auth user create bob@example.com --password "SecurePass123"cohera auth user delete
Section titled “cohera auth user delete”Delete a user account.
Usage:
cohera auth user delete <email|user-id>Flags:
--force- Skip confirmation prompt--keep-content- Delete user but preserve their content
Examples:
# Delete user with confirmationcohera auth user delete alice@example.com
# Force delete without confirmationcohera auth user delete alice@example.com --forcecohera auth group create
Section titled “cohera auth group create”Create a new group or subgroup.
Usage:
cohera auth group create <name> [options]Flags:
--slug <slug>- URL-friendly identifier--parent <group-slug>- Create as subgroup of existing group--visibility <public|private|secret>- Group visibility (default: public)--description <text>- Group description
Examples:
# Create top-level groupcohera auth group create "Gaming Community" --slug gaming
# Create subgroupcohera auth group create "Speedrunners" --slug speedrunners --parent gaming
# Create private subgroup with descriptioncohera auth group create "Core Team" --slug core --parent gaming --visibility private --description "Invitation only"The user running this command is automatically added as the initial admin of the created group.
cohera auth group add-member
Section titled “cohera auth group add-member”Add a user to a group with a specific role.
Usage:
cohera auth group add-member <group-slug> <user-email> --role <role>Flags:
--role <role>- Role to assign (required)
Examples:
# Add user as moderatorcohera auth group add-member gaming-forum alice@example.com --role moderator
# Add user as membercohera auth group add-member cooking-forum alice@example.com --role membercohera auth group remove-member
Section titled “cohera auth group remove-member”Remove a user from a group.
Usage:
cohera auth group remove-member <group-slug> <user-email>Examples:
# Remove user from groupcohera auth group remove-member gaming-forum alice@example.comcohera auth role create
Section titled “cohera auth role create”Create a new role definition.
Usage:
cohera auth role create <role-name> [permissions...]Flags:
--description <text>- Role description
Examples:
# Create content editor rolecohera auth role create editor posts.create posts.edit posts.moderate
# Create role with descriptioncohera auth role create contributor "posts.create" --description "Community contributors"API Reference
Section titled “API Reference”hasPermission()
Section titled “hasPermission()”Check if a user has a specific permission in a group context.
function hasPermission( userId: string, groupId: string, permission: string,): Promise<boolean>;Parameters:
userId- The user to checkgroupId- The group contextpermission- Permission string to validate (e.g.,'posts.delete')
Returns: true if user has permission, false otherwise
Example:
import { hasPermission } from "@cohera/auth/api";
// Check before allowing deletionif (await hasPermission(userId, post.groupId, "posts.delete")) { await deletePost(postId);} else { throw new Error("Insufficient permissions");}requirePermission()
Section titled “requirePermission()”Middleware helper that throws an error if permission check fails.
function requirePermission( userId: string, groupId: string, permission: string,): Promise<void>;Parameters:
userId- The user to checkgroupId- The group contextpermission- Required permission string
Throws: UnauthorizedError if user lacks permission
Example:
import { requirePermission } from '@cohera/auth/api'
// In a tRPC procedureasync deletePost(postId: string) { const post = await db.posts.findById(postId)
// Will throw if user lacks permission await requirePermission(ctx.userId, post.groupId, 'posts.delete')
await db.posts.delete(postId)}getUserGroups()
Section titled “getUserGroups()”Get all groups a user belongs to with their roles.
function getUserGroups(userId: string): Promise<GroupMembership[]>;
interface GroupMembership { groupId: string; groupName: string; groupSlug: string; role: string; permissions: string[]; joinedAt: Date;}Example:
import { getUserGroups } from "@cohera/auth/api";
const groups = await getUserGroups(userId);
// Find groups where user is adminconst adminGroups = groups.filter((g) => g.role === "admin");getGroupMembers()
Section titled “getGroupMembers()”Get all members of a group with their roles.
function getGroupMembers(groupId: string): Promise<Member[]>;
interface Member { userId: string; email: string; name?: string; role: string; permissions: string[]; joinedAt: Date;}Example:
import { getGroupMembers } from "@cohera/auth/api";
const members = await getGroupMembers(groupId);
// Get all moderatorsconst moderators = members.filter((m) => m.role === "moderator");getSubGroups()
Section titled “getSubGroups()”Get immediate subgroups of a group.
function getSubGroups(groupId: string): Promise<Group[]>;Parameters:
groupId- The parent group ID
Returns: Array of immediate child groups (does not include nested descendants)
Example:
import { getSubGroups } from "@cohera/auth/api";
const subgroups = await getSubGroups("acme-corp");
// Returns: [{ id: "engineering", name: "Engineering", ... }, { id: "marketing", ... }]getGroupAncestors()
Section titled “getGroupAncestors()”Get the parent chain of a group, from immediate parent to root.
function getGroupAncestors(groupId: string): Promise<Group[]>;Parameters:
groupId- The group to find ancestors for
Returns: Array of ancestor groups, ordered from immediate parent to root. Empty array if group has no parent.
Example:
import { getGroupAncestors } from "@cohera/auth/api";
const ancestors = await getGroupAncestors("frontend-team");
// Returns: [// { id: "engineering", name: "Engineering", ... }, // immediate parent// { id: "acme-corp", name: "Acme Corporation", ... } // root// ]Middleware Integration
Section titled “Middleware Integration”Other modules use auth middleware to protect routes and check permissions:
import { authMiddleware } from "@cohera/auth/middleware";
export const handle = authMiddleware({ // Make user available in all routes attachUser: true,
// Redirect unauthenticated users redirectTo: "/login",
// Paths that don't require auth publicPaths: ["/", "/about", "/login", "/signup"],});In route handlers:
import { requirePermission } from "@cohera/auth/api";import type { RequestHandler } from "./$types";
export const DELETE: RequestHandler = async ({ params, locals }) => { const { id, slug } = params; const userId = locals.user.id;
const post = await db.posts.findById(id);
// Check permission before deletion await requirePermission(userId, post.groupId, "posts.delete");
await db.posts.delete(id);
return new Response(null, { status: 204 });};import { getSession } from "@cohera/auth/api";
export async function createContext(event: RequestEvent) { const session = await getSession(event.cookies);
return { db, userId: session?.userId, session, };}
// Protected procedureimport { requirePermission } from "@cohera/auth/api";import { TRPCError } from "@trpc/server";
export const postsRouter = router({ delete: protectedProcedure .input( z.object({ postId: z.string(), groupId: z.string(), }), ) .mutation(async ({ input, ctx }) => { // Validate permission const hasAccess = await hasPermission( ctx.userId, input.groupId, "posts.delete", );
if (!hasAccess) { throw new TRPCError({ code: "FORBIDDEN" }); }
await ctx.db.posts.delete(input.postId); }),});