Skip to content

Auth Service API

Current Endpoints

  • GET /healthz
  • GET /readyz
  • GET /v1
  • POST /v1/auth/register
  • POST /v1/auth/login
  • POST /v1/auth/refresh
  • POST /v1/auth/logout
  • GET /v1/auth/me

Native Auth Foundation

Phase 8 starts with a native auth credential/session foundation. This is an internal /v1 service contract, not a public /api/auth/* cutover.

Legacy evidence:

  • apps/api/src/modules/auth/auth.controller.ts:31-88 maps local accounts, register, login, Google auth, test login, refresh, and logout routes.
  • apps/api/src/modules/auth/auth.controller.ts:189-245 maps GET /api/auth/me, profile update, and password change under JwtAuthGuard.
  • apps/api/src/modules/auth/auth.service.ts:58-83 normalizes login email, validates loginSchema, and returns authPayload.
  • apps/api/src/modules/auth/auth.service.ts:686-737 builds the session response with accessToken, refreshToken, tokenType, expiresIn, user memberships, and defaultOrganizationId.
  • apps/api/src/modules/app-data/app-data.identity-core.ts:92-188 creates users, verifies bcrypt password hashes, stores bcrypt-hashed refresh tokens, consumes refresh tokens by revoking them, and revokes logout tokens.
  • apps/api/src/common/auth.guard.ts:41-78 accepts access tokens from the Bearer header or hoctapaz.accessToken cookie and requires type=access, sub, role, and fullName.
  • apps/api/prisma/schema.prisma:10-109 defines User credential/session fields and RefreshToken.
  • packages/shared/src/index.ts:971-982 defines registerSchema and loginSchema.
  • apps/web/components/auth/login-form.tsx:63-83 and apps/web/components/auth/register-form.tsx:83-104 show the frontend expects { success: true, data: session }.

Native contract:

  • POST /v1/auth/register creates a native auth identity, hashes the password with bcrypt, creates a default local membership, stores a refresh-token hash, and returns a legacy-compatible session envelope.
  • POST /v1/auth/login lowercases and trims the email, verifies the bcrypt password hash, stores a refresh-token hash, and returns the same session envelope.
  • POST /v1/auth/refresh accepts refreshToken from the JSON body or Authorization: Bearer ..., verifies the token signature and jti, consumes the matching stored refresh token exactly once, and returns a new session with a rotated refresh token.
  • POST /v1/auth/logout accepts the same refresh-token sources and returns { "revoked": false } when the token is missing, invalid, already revoked, or does not match the stored hash.
  • GET /v1/auth/me accepts Authorization: Bearer <accessToken> and returns the current native identity in the same envelope shape.

Session envelope:

json
{
  "success": true,
  "data": {
    "user": {
      "id": "usr_123",
      "accountCode": "HTA12345678",
      "email": "[email protected]",
      "fullName": "Student One",
      "role": "STUDENT",
      "status": "ACTIVE",
      "createdAt": "2026-07-04T00:00:00Z",
      "updatedAt": "2026-07-04T00:00:00Z",
      "memberships": [
        {
          "organizationId": "org_local_center",
          "organizationName": "HOCTAPAZ Local Center",
          "role": "STUDENT",
          "status": "ACTIVE"
        }
      ],
      "defaultOrganizationId": "org_local_center"
    },
    "accessToken": "<token>",
    "refreshToken": "<token>",
    "tokenType": "Bearer",
    "expiresIn": 86400
  },
  "message": "Logged in"
}

Native token notes:

  • Access token payload keeps the legacy fields needed by the frontend and guards: sub, type=access, role, accountCode, fullName, email, and defaultOrganizationId.
  • Refresh token payload keeps sub, type=refresh, and jti.
  • Refresh tokens are SHA-256 digested before bcrypt hashing so long JWT strings are never stored raw and do not hit Go bcrypt's 72-byte input limit.
  • Native JWT signing uses the auth-service secret configuration; production must provide a non-default secret before public cutover.
  • Local compose reads AUTH_JWT_SECRET with a development default; local K8s reads it from platform-secrets.

Database:

  • services/auth-service/migrations/000002_auth_identity.sql creates identities, identity_memberships, and refresh_tokens.
  • legacy_id is nullable during native-only local creation and required during backfill/migration reports.
  • identities.email and non-empty identities.phone are unique.
  • refresh_tokens.token_id is unique and stores a bcrypt hash, not the raw token.

Validation queries:

sql
SELECT id, legacy_id, account_code, email, role, status
FROM identities
ORDER BY created_at DESC
LIMIT 20;

SELECT user_id, token_id, expires_at, revoked_at
FROM refresh_tokens
ORDER BY created_at DESC
LIMIT 20;

SELECT user_id, organization_id, role, status
FROM identity_memberships
ORDER BY joined_at DESC
LIMIT 20;

Rollback for this native slice:

  • Keep public /api/auth/* routed to legacy.
  • Disable gateway/native callers for /v1/auth/*.
  • Drop auth-service local identity tables with the migration down step if local test data must be reset.

Non-goals for P8-001:

  • Google auth provider settings and account linking.
  • Forgot/reset password email flow.
  • Profile editing, role-specific profiles, teacher KYC, and email-change verification.
  • Cross-service school/user ownership finalization.
  • Public gateway adapter/cutover for /api/auth/*.

Gateway Rehearsal

deploy/gateway/routes.auth-native-example.json and deploy/gateway/routes.auth-native-localhost-example.json provide non-default route-table rehearsals for the native session foundation. They route only POST /api/auth/register, POST /api/auth/login, POST /api/auth/refresh, POST /api/auth/logout, and GET /api/auth/me to auth-service; unsupported auth siblings stay legacy-proxied.

Run make test-auth-routes before any live or browser session rehearsal.

Go-platform documentation is generated from repository Markdown.