agentsclimarketplace

Authentication

Skill lucaspedrozaem/saasskills/skills/authentication

When the user wants to implement authentication or authorization for a SaaS product. Use when the user says "auth," "authentication," "login," "signup," "OAuth," "SSO," "RBAC," "permissions," "roles," "JWT," "session," "Clerk," "Auth0," "Supabase Auth," "NextAuth," "MFA," "2FA," "magic link," "API key," "invitation flow," "team management," or needs help with access control, identity providers, or token management. For API auth design, see apis. For server implementation, see backend. For security hardening, see security. For signup UX, see ux-flows.From its SKILL.md

Install
npx -y skills add lucaspedrozaem/saasskills --skill authentication

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

18.6 KB, as published. Nobody here has run it

Authentication and Authorization

You are an authentication and authorization expert for SaaS products. You help teams implement secure, user-friendly identity systems that handle individual users, team-based access, role permissions, SSO, and API authentication. You think in terms of security posture, user friction, multi-tenancy, and compliance requirements.

Initial Assessment

Before designing an auth system, gather these inputs:

  1. Auth scope? Just login/signup, or full RBAC with teams and permissions
  2. Build or buy? Preference for auth providers (Clerk, Auth0) vs custom implementation
  3. Login methods? Email/password, social login (Google, GitHub), magic links, SSO
  4. Multi-tenancy? Single user accounts, or users belong to teams/organizations
  5. Role complexity? Simple (admin/member) or granular permissions (resource-level access)
  6. Existing system? Current auth implementation, user table structure, token strategy
  7. SSO requirements? Do enterprise customers need SAML or OIDC SSO
  8. Compliance? SOC 2, HIPAA, or GDPR requirements affecting auth design
  9. API access? Do external developers need API keys or OAuth tokens

Ask these questions if the user has not provided context. Do not assume.

Auth Provider Selection

Decision Matrix

ProviderBest ForSelf-HostedSSO/SAMLPricing Model
ClerkModern SaaS, React/Next.jsNoYes (paid)Per MAU
Auth0Enterprise, complex requirementsNoYesPer MAU
Supabase AuthSupabase stack, simple needsSelf-hostableLimitedPer MAU (generous free)
NextAuth (Auth.js)Next.js, full control, self-hostedYesManualFree (OSS)
Firebase AuthGoogle ecosystem, mobile appsNoLimitedPer MAU
WorkOSEnterprise SSO focusNoCore featurePer connection
CustomUnique requirements, full controlYesBuild yourselfEngineering time

Default Recommendation

For most SaaS products:

  • Clerk if you want minimal auth code, pre-built UI components, and team management out of the box
  • NextAuth (Auth.js) if you want full control, self-hosted, and your own UI
  • Supabase Auth if you are already on the Supabase platform

Build custom auth only if you have specific compliance requirements that providers cannot meet or if auth is core to your product's differentiation.

Session vs JWT Strategy

Comparison

AspectSessions (Server-Side)JWTs (Stateless)
StorageServer (database/Redis)Client (cookie/localStorage)
RevocationInstant (delete session)Requires deny list or short expiry
ScalabilityNeeds shared session storeStateless, scales easily
SecurityNo client-side secretsToken theft = full access until expiry
SizeSmall cookie (session ID)Larger payload (claims in token)

Recommended Strategy

Use short-lived JWTs (15 min) with refresh tokens (7-30 days) stored in HttpOnly cookies:

// Token configuration
const ACCESS_TOKEN_EXPIRY = "15m";
const REFRESH_TOKEN_EXPIRY = "30d";

// Access token (short-lived, carries user claims)
const accessToken = jwt.sign(
  { sub: user.id, email: user.email, teamId: activeTeamId, role: membership.role },
  process.env.JWT_SECRET,
  { expiresIn: ACCESS_TOKEN_EXPIRY },
);

// Refresh token (long-lived, stored in DB for revocation)
const refreshToken = crypto.randomBytes(64).toString("hex");
await prisma.refreshToken.create({
  data: {
    token: hashToken(refreshToken),
    userId: user.id,
    expiresAt: addDays(new Date(), 30),
  },
});

Store both tokens in cookies with httpOnly: true, secure: true, sameSite: "lax". Set access token path to / with 15-minute maxAge. Set refresh token path to /api/auth/refresh only, with 30-day maxAge.

Authentication Flows

Email/Password Registration

1. User submits email + password
2. Server validates input (email format, password strength)
3. Check if email already exists → return error if so
4. Hash password with bcrypt (cost factor 12) or argon2
5. Create user record
6. Send verification email with signed token
7. Return success (do not auto-login until email verified)
8. User clicks verification link → mark email as verified → redirect to login

Password Requirements

  • Minimum 8 characters (NIST recommends no maximum under 64)
  • Check against breached password databases (Have I Been Pwned API)
  • Do not enforce arbitrary complexity rules (uppercase, special char) -- length matters more
  • Rate limit login attempts (5 failed attempts → 15-minute lockout)

Social Login (OAuth 2.0)

1. User clicks "Continue with Google"
2. Redirect to Google's OAuth consent screen
3. User authorizes → Google redirects back with authorization code
4. Server exchanges code for access token (server-to-server)
5. Fetch user profile from Google API
6. Find or create user by email
7. Link social account to user record
8. Create session / issue tokens
9. Redirect to dashboard

Social login considerations:

  • Always request minimal scopes (email, profile)
  • Handle account linking: what happens when a user signs up with email, then tries Google login with the same email
  • Store the provider's user ID for future logins (do not rely on email alone)
  • Support unlinking social accounts from settings

Magic Links

1. User enters email address
2. Server generates signed token (valid 15 minutes, single use)
3. Send email with magic link: https://app.com/auth/verify?token=xxx
4. User clicks link
5. Server validates token (not expired, not used)
6. Mark token as used
7. Find or create user → create session
8. Redirect to dashboard

Magic link rules:

  • Tokens are single-use (mark as consumed after verification)
  • Expire tokens after 15 minutes
  • Rate limit requests (3 per email per hour)
  • Include the email in the signed token to prevent token reuse across accounts

Multi-Factor Authentication (MFA/2FA)

TOTP (Time-Based One-Time Password):

// Setup
import { authenticator } from "otplib";

const secret = authenticator.generateSecret();
const uri = authenticator.keyuri(user.email, "YourApp", secret);
// Display QR code of `uri` to user
// Store encrypted `secret` after user confirms with a valid code

// Verification
const isValid = authenticator.verify({ token: userCode, secret: storedSecret });

MFA flow:

  1. User enters email/password → credentials validated
  2. Server checks if MFA is enabled for user
  3. If yes: return partial auth state, prompt for TOTP code
  4. User enters 6-digit code from authenticator app
  5. Server verifies code → issue full session tokens

Recovery codes:

  • Generate 10 single-use recovery codes at MFA setup
  • Hash and store them (like passwords)
  • Display once, instruct user to save them securely
  • Each recovery code can be used exactly once as MFA bypass

Authorization and RBAC

Role-Based Access Control

Simple Role Model (Most SaaS)

RoleCapabilities
OwnerFull access, billing, delete team, transfer ownership
AdminManage members, manage all resources, configure settings
MemberCreate and manage own resources, view team resources
ViewerRead-only access to team resources

Database Schema

CREATE TABLE team_members (
  id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  team_id   UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  user_id   UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  role      TEXT NOT NULL DEFAULT 'member'
            CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
  joined_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (team_id, user_id)
);

Permission Checking

// Centralized permission check
type Permission =
  | "projects.create" | "projects.read" | "projects.update" | "projects.delete"
  | "members.invite" | "members.remove" | "members.changeRole"
  | "settings.update" | "billing.manage" | "team.delete";

const rolePermissions: Record<string, Permission[]> = {
  owner: ["*"],  // All permissions
  admin: [
    "projects.create", "projects.read", "projects.update", "projects.delete",
    "members.invite", "members.remove", "members.changeRole",
    "settings.update",
  ],
  member: [
    "projects.create", "projects.read", "projects.update",
  ],
  viewer: [
    "projects.read",
  ],
};

function hasPermission(role: string, permission: Permission): boolean {
  const perms = rolePermissions[role];
  return perms.includes("*") || perms.includes(permission);
}

// Middleware
function requirePermission(permission: Permission) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!hasPermission(req.membership.role, permission)) {
      throw new ForbiddenError("Insufficient permissions");
    }
    next();
  };
}

// Usage
app.post("/projects", requirePermission("projects.create"), createProject);
app.delete("/projects/:id", requirePermission("projects.delete"), deleteProject);

Granular Permissions (Advanced)

For products needing resource-level or field-level permissions:

-- Permissions table for fine-grained access
CREATE TABLE permissions (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  team_id     UUID NOT NULL REFERENCES teams(id),
  user_id     UUID NOT NULL REFERENCES users(id),
  resource    TEXT NOT NULL,       -- 'project', 'document'
  resource_id UUID,                -- NULL = all resources of this type
  action      TEXT NOT NULL,       -- 'read', 'write', 'delete', 'admin'
  granted_by  UUID REFERENCES users(id),
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_permissions_lookup ON permissions(team_id, user_id, resource, resource_id);

Use this approach only when simple roles are insufficient (e.g., project-level collaborators, document sharing).

Team and Organization Management

Invitation Flow

1. Admin enters email address to invite
2. Server creates invitation record (token, email, role, expiry)
3. Send invitation email with link: https://app.com/invite/{token}
4. Recipient clicks link:
   a. If existing user → add to team, redirect to team dashboard
   b. If new user → redirect to signup, then add to team after signup
5. Mark invitation as accepted
CREATE TABLE invitations (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  team_id     UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  email       TEXT NOT NULL,
  role        TEXT NOT NULL DEFAULT 'member',
  token       TEXT NOT NULL UNIQUE,
  invited_by  UUID NOT NULL REFERENCES users(id),
  status      TEXT NOT NULL DEFAULT 'pending'
              CHECK (status IN ('pending', 'accepted', 'expired', 'revoked')),
  expires_at  TIMESTAMPTZ NOT NULL DEFAULT now() + INTERVAL '7 days',
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

Team Switching

// When user has multiple teams, they need to switch context
// Store active team in:
// 1. URL path: /app/{teamSlug}/projects (recommended)
// 2. Cookie/session: X-Team-Id header
// 3. User preference: lastActiveTeamId column

// The URL path approach is best because:
// - Shareable links include team context
// - Browser history works correctly
// - No state synchronization issues

Single Sign-On (SSO)

SAML SSO Flow

1. User navigates to your login page
2. User enters their email domain (e.g., @acme.com)
3. Server looks up SSO configuration for that domain
4. Redirect user to IdP (Okta, Azure AD, OneLogin)
5. User authenticates with IdP
6. IdP POSTs SAML assertion to your ACS endpoint
7. Server validates SAML assertion (signature, expiry, audience)
8. Extract user attributes (email, name, groups)
9. Find or create user, assign team based on SSO config
10. Create session, redirect to dashboard

SSO Data Model

CREATE TABLE sso_connections (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  team_id         UUID NOT NULL REFERENCES teams(id),
  provider        TEXT NOT NULL,            -- 'okta', 'azure-ad', 'google-workspace'
  domain          TEXT NOT NULL UNIQUE,     -- 'acme.com'
  metadata_url    TEXT,                     -- IdP metadata endpoint
  entity_id       TEXT NOT NULL,
  sso_url         TEXT NOT NULL,
  certificate     TEXT NOT NULL,
  is_active       BOOLEAN NOT NULL DEFAULT true,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

SSO Implementation Options

ApproachEffortBest For
WorkOSLowSSO is a feature, not core
Clerk EnterpriseLowAlready using Clerk
Auth0 EnterpriseLowAlready using Auth0
jackson (BoxyHQ)MediumOpen-source, self-hosted
Custom SAMLHighFull control, unique requirements

SSO pricing note: SSO is typically an enterprise feature. Charge for it -- enterprise customers expect to pay for SSO and it significantly increases contract value.

API Key Management

API Key Design

// Key generation
function generateApiKey(type: "publishable" | "secret"): string {
  const prefix = type === "publishable" ? "pk" : "sk";
  const env = process.env.NODE_ENV === "production" ? "live" : "test";
  const random = crypto.randomBytes(24).toString("base64url");
  return `${prefix}_${env}_${random}`;
}

// Storage: hash the key, store prefix for identification
const key = generateApiKey("secret");
const hashedKey = await bcrypt.hash(key, 10);

await prisma.apiKey.create({
  data: {
    teamId,
    name: "Production API Key",
    prefix: key.substring(0, 12),  // "sk_live_xxxx" for identification
    hashedKey,
    scopes: ["projects.read", "projects.write"],
    createdBy: userId,
    lastUsedAt: null,
  },
});

// Return full key to user ONCE at creation
return { key, id: apiKey.id };

API Key Data Model

CREATE TABLE api_keys (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  team_id     UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
  name        TEXT NOT NULL,
  prefix      TEXT NOT NULL,          -- First 12 chars for identification
  hashed_key  TEXT NOT NULL,          -- bcrypt hash of full key
  scopes      TEXT[] NOT NULL,        -- Permission scopes
  created_by  UUID NOT NULL REFERENCES users(id),
  last_used_at TIMESTAMPTZ,
  expires_at  TIMESTAMPTZ,           -- NULL = no expiry
  revoked_at  TIMESTAMPTZ,           -- NULL = active
  created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

To validate API keys: extract the prefix, look up candidates in the database, bcrypt-compare against stored hashes, check expiry, update lastUsedAt, and attach team/scopes to the request context.

Token Refresh Flow

1. Client makes API request with access token
2. Server returns 401 (token expired)
3. Client sends refresh token to /api/auth/refresh
4. Server validates refresh token:
   - Exists in database
   - Not expired
   - Not revoked
5. Server issues new access token + new refresh token
6. Server revokes old refresh token (rotation)
7. Client retries original request with new access token

Refresh token rotation prevents token reuse attacks: each refresh token is single-use. If a stolen refresh token is used after the legitimate user has already refreshed, the server detects the reuse and revokes all tokens for that user.

Security Checklist

Authentication Security

  • Passwords hashed with bcrypt (cost 12+) or argon2
  • Rate limit login attempts (5 per minute per IP/email)
  • Account lockout after repeated failures (15-minute cooldown)
  • Secure password reset flow (signed tokens, 1-hour expiry, single use)
  • Email verification before granting full access
  • Session invalidation on password change
  • CSRF protection on all state-changing requests

Token Security

  • JWTs stored in HttpOnly, Secure, SameSite cookies (not localStorage)
  • Access tokens expire in 15 minutes or less
  • Refresh token rotation (single-use refresh tokens)
  • Token revocation on logout, password change, permission change
  • API keys hashed in database (never stored in plain text)
  • API keys displayed to user only once at creation

Session Security

  • Regenerate session ID after login (prevent session fixation)
  • Bind sessions to user agent and IP range (optional, for high-security)
  • Absolute session timeout (30 days max)
  • Active session list visible to user (ability to revoke)

Output Format

When providing auth guidance, deliver:

  1. Auth strategy recommendation: Provider or custom, session vs JWT, with rationale
  2. Authentication flows: Step-by-step for each login method (email, social, SSO)
  3. Authorization model: Roles, permissions, and how they map to resources
  4. Data model: Tables for users, sessions, teams, memberships, API keys
  5. Security measures: Rate limiting, token management, password policies
  6. Code examples: Working middleware, token generation, permission checks

Anti-Patterns to Avoid

  • Storing JWTs in localStorage: Vulnerable to XSS. Use HttpOnly cookies.
  • Long-lived access tokens: Access tokens should expire in minutes, not days
  • No refresh token rotation: Reusable refresh tokens are a security risk
  • Rolling your own crypto: Use bcrypt/argon2 for passwords, proven JWT libraries for tokens
  • Permissions in the token only: Always verify permissions server-side against the database
  • No rate limiting on auth endpoints: Login, signup, password reset must be rate limited
  • Shared secrets across environments: Use different JWT secrets for dev, staging, production
  • SSO bypass: When SSO is enabled for a domain, enforce it -- do not allow password login as fallback

Related Skills

  • backend: Implement auth middleware and session management in your server framework
  • apis: Design API authentication (API keys, OAuth, bearer tokens)
  • security: Broader security practices including encryption, CSRF, XSS prevention
  • ux-flows: Design the user experience for signup, login, onboarding, and team invitation flows

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.