agentsclimarketplace

Auth authorization

Skill batur/ts-baseline-docs/.agents/skills/auth-authorization

TypeScript software architecture baseline with VS Code Agent Skills for AI coding, review, security, testing, API design, and delivery standards.

Install
npx -y skills add batur/ts-baseline-docs --skill auth-authorization

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.

What its author says it does

Copied from the file, not written here

Defines authentication and authorization rules for TypeScript applications. Use when adding or reviewing auth guards, AuthContext, provider adapters, role/permission checks, tenant or organization boundaries, API key auth, 401/403/404 behavior, protected routes, and server-side access control.

The file declares its own license as Proprietary. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

13.3 KB, as published. Nobody here has run it

Auth & Authorization Skill

When to use this skill

Use this skill when a task touches:

  • authentication, login state, sessions, JWTs, bearer tokens, cookies, API keys, SSO, or auth providers
  • authorization, roles, permissions, guards, policies, or access checks
  • tenant, organization, workspace, project, team, or account boundaries
  • protected API routes, protected frontend pages, middleware, route handlers, or use-cases
  • Supabase Auth, Auth.js/NextAuth, Firebase Auth, custom JWT, API key auth, or enterprise SSO
  • 401, 403, or security-sensitive 404 behavior
  • review of whether a user is allowed to read, update, delete, invite, bill, generate, export, or administer something

If the task is only UI visibility, still check whether server-side authorization exists.

Goal

Separate identity from permission.

Authentication answers:

Who is this caller?

Authorization answers:

Can this caller perform this action on this resource in this context?

Do not treat a logged-in user as automatically authorized.

Core rules

  1. Authentication and authorization are separate concerns.
  2. Authentication is centralized in guards, middleware, or provider adapters.
  3. Application/use-case code receives a normalized AuthContext.
  4. Application/use-case code must not receive raw tokens, raw sessions, provider SDK user objects, request headers, or cookies.
  5. Provider SDKs are infrastructure details and must stay behind adapters.
  6. Authorization is enforced server-side.
  7. Client-side permission checks are UX helpers only; they are not security controls.
  8. Business-sensitive use-cases must check permission before performing mutations or sensitive reads.
  9. Tenant-scoped resources must enforce organizationId / tenantId boundaries at query and use-case level.
  10. Role may be used for user management, but business authorization should be permission-based.
  11. Permissions use the resource:action format.
  12. API keys must be hashed at rest and shown only once at creation time.
  13. Missing or invalid auth returns 401.
  14. Authenticated but unauthorized access returns 403.
  15. Cross-tenant or resource-enumeration-sensitive access may return 404.

Standard AuthContext

Use a normalized auth context instead of provider-specific objects.

export type AuthType = "session" | "bearer-token" | "api-key";

export type AuthContext = {
  userId: string;
  organizationId?: string;
  roles: string[];
  permissions: string[];
  authType: AuthType;
};

For API keys:

export type ApiKeyAuthContext = {
  authType: "api-key";
  apiKeyId: string;
  organizationId: string;
  permissions: string[];
};

When a use-case needs authentication, pass AuthContext explicitly:

await updateProject({
  projectId,
  input,
  authContext,
});

Do not pass:

request.headers.authorization

Do not pass provider SDK session objects into use-cases.

Authentication provider adapters

Auth providers are interchangeable details.

Supported provider examples:

  • Supabase Auth
  • Auth.js / NextAuth
  • Firebase Auth
  • custom JWT
  • session cookie
  • API key auth
  • enterprise SSO

Use an adapter interface:

export interface AuthProvider {
  authenticate(request: Request): Promise<AuthContext | null>;
}

A route/guard can normalize provider-specific auth:

const authContext = await AUTH_GUARD.requireUser(request);

Application/domain logic must not import auth provider SDKs. Those imports belong in adapters, infrastructure, middleware, or composition root.

Guard standard

Protected routes must be explicit.

export async function updateProjectController(request: Request) {
  const authContext = await AUTH_GUARD.requireUser(request);
  const input = UPDATE_PROJECT_SCHEMA.parse(await request.json());

  const result = await updateProject({
    projectId,
    input,
    authContext,
  });

  return { data: serializeProject(result) };
}

The controller/route handler should authenticate, validate input, call a use-case, and serialize output. It should not contain business authorization rules unless the framework requires a thin guard layer.

Permission model

Prefer permission-based business authorization.

Permission format:

resource:action

Examples:

user:read
user:update
project:create
project:update
project:delete
billing:manage
organization:invite-member
api-key:create
api-key:revoke
ai-generation:create

Roles may map to permissions, but do not hard-code role checks in many places if permissions can express the rule.

Avoid:

if (authContext.roles.includes("admin")) {
  // allow everything
}

Prefer:

await PERMISSIONS.require(authContext, "project:update", {
  organizationId: project.organizationId,
});

Authorization location

Authorization must be enforced in the application/use-case layer for business-sensitive operations.

Good:

export async function updateProject(params: {
  projectId: string;
  input: UpdateProjectInput;
  authContext: AuthContext;
}) {
  const project = await PROJECT_REPOSITORY.findByIdForOrganization(
    params.projectId,
    params.authContext.organizationId,
  );

  if (!project) {
    throw new AppError(PROJECT_ERROR_CODE.PROJECT_NOT_FOUND, "Project not found.", 404);
  }

  await PERMISSIONS.require(params.authContext, "project:update", {
    organizationId: project.organizationId,
  });

  return PROJECT_REPOSITORY.update(project.id, params.input);
}

Bad:

// UI hides the button, so backend does not check permission.

Bad:

// Controller checks a role, but the use-case can be called elsewhere without authorization.

Tenant and organization boundary

For multi-tenant systems, permission checks are not enough. Queries must also be tenant-safe.

Prefer repository methods like:

findByIdForOrganization(id: string, organizationId: string)

Avoid tenant-scoped generic methods like:

findById(id: string)

unless they are clearly internal/admin-only:

findByIdForAdmin(id: string)

Tenant-scoped resource access must include organizationId or tenantId in the query.

For cross-tenant access, prefer 404 when resource existence should not be leaked.

HTTP status behavior

Use this status mapping:

SituationStatus
No token/session/API key401
Invalid token/session/API key401
Expired token/session401
Authenticated but missing permission403
Resource does not exist404
Resource belongs to another tenant and existence should be hidden404
Resource exists but user is not allowed and existence is not sensitive403
API key lacks required scope403

Use the standard error envelope:

{
  "error": {
    "code": "FORBIDDEN",
    "message": "You do not have permission to perform this action.",
    "requestId": "req_123"
  }
}

Do not return provider raw auth errors to clients.

API key auth

API keys are credentials and must be handled as secrets.

Rules:

  1. Store only a hash of the API key.
  2. Show the raw key only once when created.
  3. Use a prefix for identification and environment separation.
  4. Associate keys with organization/tenant context.
  5. Assign explicit permissions/scopes.
  6. Support revocation.
  7. Never log raw API keys.
  8. Never return raw API keys after creation.

Example prefix style:

sk_live_...
sk_test_...

Supabase Auth notes

When using Supabase:

  • Supabase is an auth provider adapter, not a domain dependency.
  • Supabase JS client must not leak into domain/application logic.
  • Client-side anon access must rely on reviewed RLS policies.
  • Service role key is server-only.
  • Server privileged operations must go through backend adapters/use-cases.
  • RLS does not replace application-level business authorization when use-cases execute privileged operations.

Firebase Auth notes

When using Firebase:

  • Firebase Auth/Admin SDK belongs in an adapter.
  • Firestore Security Rules are security controls and must be reviewed/tested.
  • Backend use-cases still require explicit permission checks when using admin privileges.
  • Do not trust client-side checks alone.

Auth transport selection

Do not force one transport for every project.

Use these defaults:

  • frontend/fullstack apps: session cookie or provider session can be used
  • public/backend APIs: bearer token or API key can be used
  • automation/service integrations: API key or signed webhook can be used
  • enterprise apps: SSO can be added behind the provider adapter

The normalized AuthContext remains stable regardless of transport.

Coding workflow

When adding or changing auth-related code:

  1. Identify whether the task is authentication, authorization, or both.
  2. Determine the caller type: user session, bearer token, API key, webhook, system job, or admin.
  3. Normalize caller identity into AuthContext.
  4. Validate input before use-case logic.
  5. Load tenant-scoped resources with tenant-safe repository/query methods.
  6. Perform server-side permission checks in the use-case/application layer.
  7. Return 401/403/404 according to the status rules.
  8. Do not leak provider error details, resource existence, tokens, or secrets.
  9. Add or update tests for protected paths, forbidden paths, and tenant boundaries.
  10. Update OpenAPI security definitions if the API contract changed.
  11. Update docs/ADR if the auth model or permission model changed.

Review checklist

Check every auth-related change for:

  • Does application/use-case code receive AuthContext, not raw token/session/header/cookie?
  • Is authentication centralized in guard/middleware/provider adapter?
  • Is provider SDK usage isolated to adapter/infrastructure/composition code?
  • Is authorization enforced server-side?
  • Does the use-case check permission before sensitive read/write?
  • Is the permission expressed as resource:action?
  • Are role checks limited and not used as broad bypasses?
  • Are tenant-scoped queries filtered by organizationId or tenantId?
  • Does cross-tenant access avoid leaking resource existence when needed?
  • Are 401, 403, and 404 used correctly?
  • Are API keys hashed and never logged?
  • Are API key scopes/permissions explicit?
  • Are Supabase RLS / Firestore Security Rules reviewed when used?
  • Are tests added for unauthenticated, unauthorized, allowed, and cross-tenant cases?
  • Is OpenAPI security metadata updated for protected endpoints?

Required tests

For protected endpoints/use-cases, add tests for:

  • unauthenticated request returns 401
  • invalid token/session/API key returns 401
  • authenticated user missing permission returns 403
  • cross-tenant resource access returns 404 or 403 according to project policy
  • allowed permission succeeds
  • tenant-scoped query cannot access another organization’s resource
  • API key auth works with valid key
  • API key missing scope returns 403
  • API key raw value is not stored
  • provider adapter maps provider failures safely

Business-sensitive use-cases must not be merged without authorization tests.

Good examples

Use-case receives AuthContext

export async function createProject(params: {
  input: CreateProjectInput;
  authContext: AuthContext;
}) {
  await PERMISSIONS.require(params.authContext, "project:create", {
    organizationId: params.authContext.organizationId,
  });

  return PROJECT_REPOSITORY.createForOrganization({
    organizationId: params.authContext.organizationId,
    name: params.input.name,
  });
}

Tenant-safe read

const project = await PROJECT_REPOSITORY.findByIdForOrganization(
  projectId,
  authContext.organizationId,
);

if (!project) {
  throw new AppError(PROJECT_ERROR_CODE.PROJECT_NOT_FOUND, "Project not found.", 404);
}

API key hashed storage

const rawKey = createApiKeySecret();
const keyHash = await hashSecret(rawKey);

await API_KEY_REPOSITORY.create({
  keyHash,
  prefix: getKeyPrefix(rawKey),
  organizationId,
  permissions,
});

Anti-patterns

Do not:

  • pass raw Authorization headers into use-cases
  • pass provider SDK user/session objects into use-cases
  • import Supabase/Firebase/Auth.js/JWT SDKs in domain/application logic
  • rely on frontend button hiding as authorization
  • use role === "admin" as a broad bypass without permission reasoning
  • query tenant-scoped resources by ID alone
  • store API keys in plain text
  • log tokens, cookies, sessions, API keys, or auth provider raw errors
  • return raw provider auth errors to clients
  • return 403 for cross-tenant access when resource enumeration is sensitive
  • allow background jobs/system users to bypass permissions without explicit system context

Related skills

Use together with:

  • security-baseline for secure-by-default checks
  • api-design for protected endpoint design
  • error-handling for 401/403/404 error mapping
  • validation for auth input validation
  • database-persistence for tenant-safe persistence access
  • testing for auth and tenant boundary tests
  • openapi for security scheme documentation

Keep looking

Skills are one crate of 328,083. 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.