Login & JWT (Auth)
Overview
The auth module is the front door for all of cms-web, and it serves two purposes at once.
- It provides the endpoints for login, LINE OA selection, token refresh, and token revocation.
- It provides the global JWT guard that the router wraps around the
authedroute group of every module.
The defining characteristic of this system is that login happens in two steps. The first step
yields a token not yet bound to any LINE OA; in the second step the user picks an OA and receives a
token carrying lineOaId in its claims. That is what lets every tenant-scoped route insist the user
has chosen an OA first.
Business Flow
POST /api/auth/login— verifies email and password with bcrypt, then issues an access token and a refresh token. At this stage the token has nolineOaIdclaim.- cms-web shows the list of LINE OAs the user can access, and the user picks one.
POST /api/auth/login-with-line-oaruns behind theJwtLoginAuthguard, which accepts tokens withoutlineOaId, and issues a fresh token pair carryinglineOaId,lineOaHash,organizationId, androleId. If that OA has nolineOaHashyet, the system generates one and writes it back to theline_oatable as a backfill.- Every issued token is cached in the Redis hash
h_session:<userId>, storing only the first 15 characters of SHA256(token) in theaccessTokenandrefreshTokenfields. - Every request hitting the
authedgroup passes throughJwtAuth, which verifies the signature, requireslineOaId, populates CLS, and compares the hash against Redis. A mismatch — because the token was revoked or a newer login superseded it — returns 401 with codeAPP_001. POST /api/auth/refresh-access-tokenruns behindRefreshTokenAuth. It requires a refresh token still present in Redis and annbfclaim before issuing a new access token.POST /api/auth/revoke-tokendeletes theh_session:<userId>key, invalidating every token belonging to that user immediately.
Key Files & Functions
The code lives in internal/modules/auth/, comprising controller.go, service.go, guards.go,
jwt.go, hash.go, lineoa.go, oaaccess.go, and module.go.
These routes are registered directly from cmd/api/main.go rather than through featureModules,
because the module also has to supply the router's shared guards.
| Method | Route | Handler | Guard |
|---|---|---|---|
| POST | /api/auth/login | m.login | none (public) |
| POST | /api/auth/login-with-line-oa | m.loginWithLineOa | JwtLoginAuth |
| POST | /api/auth/refresh-access-token | m.refreshAccessToken | RefreshTokenAuth |
| POST | /api/auth/revoke-token | m.revokeToken | none (a legacy TODO notes the admin role check is still missing) |
Guards exported for other modules (internal/modules/auth/guards.go):
(*Module).JwtAuth()— the global guard on theauthedgroup; requireslineOaId(*Module).JwtLoginAuth()— guard for routes reachable before an OA is chosen(*Module).RefreshTokenAuth()— guard for the refresh flowauth.SuperAdmin()— requiresroleIdto equal 1auth.InternalApiKey()— checks theX-Internal-Keyheader against theINTERNAL_API_KEYenvironment variable
Other exported helpers include auth.GenerateLineOaHash, auth.GenerateShortHash, and the
app.AuthTokenService interface (IsAllowToken, RevokeToken).
Connections to Other Services
- Permission — the routes themselves need no permission since they are public, but this module populates the CLS values that the entire permission system depends on.
- Tables —
user,line_oa,user_line_oa(per-user OA access), andsystem_role. - Redis — the
h_session:<userId>hash holding token hashes, namespaced byREDIS_NAMESPACE. - Key environment variables —
JWT_SECRET(missing it prevents boot) andINTERNAL_API_KEY. - Error codes — 401 returns
APP_001; a failed refresh returns 400 withAPP_002orAPP_003. - Related pages — User Permissions & Module Gate, and User Management.