Auth API#
Legacy password verify + Cube-2.0-owned rehash + JWT issue + login audit.
- Endpoint:
POST /api/v1/auth/login - Auth: none (this issues the token)
- Jira: BFC-13
- ADRs: 0001 (auth model, amended), 0005 (claim mapping), 0006 (rehash persistence)
- Legacy source:
aspnet_Membership+aspnet_Users+aspnet_Profile+MnxUserAuth(new)
Flow#
sequenceDiagram
autonumber
participant Client
participant AuthCtrl as AuthController
participant Svc as UserAuthService
participant Hasher as PasswordHasher
participant Rec as LoginActivityRecorder
participant Tok as TokenService
participant DB as SQL
Client->>AuthCtrl: POST /api/v1/auth/login {username, password}
AuthCtrl->>Svc: AuthenticateAsync(ctx)
Svc->>DB: SELECT MnxUserAuth WHERE username
alt MnxUserAuth row exists
Svc->>Hasher: VerifyPbkdf2(password, hash)
else no MnxUserAuth row
Svc->>DB: SELECT aspnet_Membership WHERE username
Svc->>Hasher: VerifyEncrypted(password, storedPw)
Note over Svc,DB: On success:<br/>INSERT MnxUserAuth (Pbkdf2 hash)
end
alt IsLockedOut or IsSuspended (checked AFTER verify)
Svc->>Rec: Record(LockedOut / Suspended)
Svc-->>AuthCtrl: LoginOutcome.LockedOut / Suspended
AuthCtrl-->>Client: 401
else Verify failed
Svc->>Rec: Record(BadPassword)
Svc-->>AuthCtrl: LoginOutcome.BadPassword
AuthCtrl-->>Client: 401
else Success
Svc->>Tok: IssueToken(userId)
Tok->>DB: Resolve groups, roles, mod_admin, itar<br/>(per ADR 0005)
Tok-->>Svc: JWT
Svc->>Rec: Record(Success)
Svc-->>AuthCtrl: LoginOutcome.Success + JWT
AuthCtrl-->>Client: 200 { accessToken, claims }
endRequest#
POST /api/v1/auth/login
Content-Type: application/json
{ "username": "compAdmin", "password": "***" }Response — success#
{
"success": true,
"data": {
"accessToken": "eyJhbGciOi...",
"tokenType": "Bearer",
"expiresIn": 3600,
"claims": {
"sub": "<user-guid>",
"unique_name": "compAdmin",
"role": ["SalesOrder.View", "..."],
"grp": ["<group-guid>", "..."],
"mod_admin": ["SGA", "MFG", "..."],
"itar": true
}
}
}Response — failure#
Always 401 Unauthorized regardless of why — no user enumeration. The reason is recorded in MnxLoginActivity (server-side only) for support triage.
Rules#
- Order matters: password is verified before the suspended / locked-out check. This prevents timing side-channels that could enumerate usernames.
- Rehash on login: legacy
Encryptedpasswords verified againstaspnet_Membershiptrigger a one-time write toMnxUserAuthwith a PBKDF2 hash. All subsequent logins useMnxUserAuth. Legacy rows are never modified (per ADR 0004). CompanyAdminexpansion: at token-issue time,CompanyAdmin=trueexpands tomod_admin=[SGA, MFG, SCM, CRM, ENG, ACCT]so downstream authorization doesn’t need special-case logic.itarclaim: sourced fromaspnet_Profile.IsItarAuthorized. Data-scope filter, not a role.
Related follow-ups#
- BFC-31 — machine keys still in
appsettings.json(blocked on secrets store) - BFC-34 — failed-attempt tracking + auto-lockout (blocked on product)