Skip to content

Thiết kế: Authentication Feature

Linear Issue: N/A Status: Production

1. Tổng quan kiến trúc

1.1 Cấu trúc hệ thống

flowchart TB
    subgraph Client["Mobile/Web Client"]
        App["Mobile App / Web"]
    end

    subgraph Auth["authentication module (port 8085)"]
        AC["AuthController\n/api/auth/*"]
        LF["LoginHandlerFactory"]
        W3["Web3AuthLoginHandler\n(WEB3_AUTH, DM2, SOLANA)"]
        IM["ImmutableLoginHandler\n(IMMUTABLE)"]
        AS["AuthService\n(core logic)"]
        SEC["SecurityJwtService\n(JWT)"]
        ASYNC["AuthAsyncService\n(@Async ops)"]
    end

    subgraph Infra["Shared Infrastructure"]
        Redis[("Redis\nSession + Lock")]
        MySQL[("MySQL\nUser/Wallet")]
        FCM["Firebase\nPush Notification"]
        Node["NodeServerService\nBlockchain"]
        Email["EmailService"]
    end

    App -->|"POST /api/auth/generate-code"| AC
    App -->|"POST /api/auth/sign-in\n(x-api-key)"| AC
    App -->|"DELETE /api/auth/logout/{token}"| AC
    AC --> LF
    LF --> W3 & IM
    W3 & IM --> AS
    AS --> SEC
    AS --> ASYNC
    SEC -->|"access_token + refresh_token"| AC
    AS --> Redis
    AS --> MySQL
    ASYNC --> FCM
    ASYNC --> Node
    ASYNC --> Email

1.2 Các thay đổi chính khi mở rộng provider

Mục Hiện tại Khi thêm provider mới
Handler Web3AuthLoginHandler, ImmutableLoginHandler Tạo XxxLoginHandler implements LoginHandler
Factory Map key = type.getHandlerKey() + "LoginHandler" Đăng ký bean mới với naming convention
Wallet type BICONOMY/DM2/SOLANA/IMMUTABLE Thêm enum WalletType + branch trong getWalletType()
LoginEnum LoginType enum Thêm value mới

2. Domain model

2.1 UserModel (các field liên quan auth)

// Key fields in UserModel
String id;
String email;
UserEnum.Status status;     // ACTIVE | NO_ACTIVE | DELETED
String authCode;            // 6-char random code
Instant expireAuthCode;     // code TTL = 5 phút
Integer loginSession;       // 0=new, 1=first, ≥2=subsequent
Instant firstLoginAt;
LoginEnum.LoginType latestLoginType;
String avatarUrl;
String referralCode;
String ipAddress;
String languageCode;

2.2 UserWalletModel

String userId;
LoginEnum.LoginType loginMethod;    // source provider
UserWalletEnum.WalletType walletType; // BICONOMY | DM2 | SOLANA | IMMUTABLE
String publicAddress;
String verifier;
String verifierId;
String aggregateVerifier;
String appPubKey;

2.3 CodeLoginResponse

String id;          // userId
String code;        // 6-char auth code
String verifierId;
String loginType;
Instant createTime;
Instant expiredAt;
boolean isFirstLogin;
String accessToken;
String refreshToken;
String message;     // optional

3. Sequence diagrams

3.1 generate-code (new user)

sequenceDiagram
    participant C as Client
    participant AC as AuthController
    participant LF as LoginHandlerFactory
    participant H as Web3AuthLoginHandler
    participant AS as AuthService
    participant AA as AuthAsyncService
    participant DB as MySQL
    participant R as Redis

    C->>AC: POST /api/auth/generate-code\n{login_type, id_token, device_type, firebase_token}
    AC->>LF: getHandler("WEB3_AUTH")
    LF-->>AC: Web3AuthLoginHandler
    AC->>H: generateCode(baseMap, request)
    H->>H: validate/decode id_token (Jose/JWT)
    H->>AS: getUserModelByEmail(email)
    AS->>DB: query UserWallet by email
    DB-->>AS: empty → new user
    AS-->>H: null
    H->>AS: setLoginCodeResponse(...)

    AS->>R: lock(USER_EMAIL_LOCKER:{verifierId})
    AS->>DB: INSERT UserModel
    AS->>AA: createUserWallet(loginType)
    AA->>DB: INSERT UserWalletModel
    AS->>AA: initializeUserSystem(userId)
    AS->>AA: sendWelcomeEmail()
    AS->>AA: assignNftOwner()
    AS->>AS: generateAndSaveAuthCode (TTL 5m)
    AS->>AS: SecurityJwtService.createUserAuthResponse()
    AS->>R: createOrUpdateSession(userId, deviceType, firebaseToken)
    AS-->>H: CodeLoginResponse
    H-->>AC: response
    AC-->>C: Result.OK(CodeLoginResponse)

3.2 generate-code (existing user)

sequenceDiagram
    participant C as Client
    participant H as Web3AuthLoginHandler
    participant AS as AuthService
    participant DB as MySQL
    participant R as Redis

    C->>H: generateCode (email đã tồn tại)
    H->>AS: getUserModelByEmail(email)
    AS->>DB: query → found UserModel
    AS-->>H: UserModel
    H->>AS: getLoginCodeResponse(userModel, ...)
    AS->>AS: handleAvatarUrlAndUserName(userModel)
    AS->>AS: handleUserWallet (update/create)
    AS->>AS: handleSecondaryWallet (async)
    AS->>AS: generateAndSaveLoginCodeAndLoginType()
    AS->>AS: SecurityJwtService.createUserAuthResponse()
    AS->>R: createOrUpdateSession()
    AS-->>H: CodeLoginResponse
    H-->>C: Result.OK(CodeLoginResponse)

3.3 logout

sequenceDiagram
    participant C as Client
    participant AC as AuthController
    participant UDTV as UserDeviceTokenService
    participant AS as AuthService
    participant R as Redis

    C->>AC: DELETE /api/auth/logout/{firebaseToken}\n(Authorization: Bearer <jwt>)
    AC->>AC: getLoginUserId() from JWT
    AC->>UDTV: findBy(userId, firebaseToken)
    UDTV-->>AC: UserDeviceTokenModel
    AC->>UDTV: deleteById(id)
    AC->>AS: invalidateUserSession(userId)
    AS->>R: sessionRepository.invalidateSession(userId)
    AC-->>C: Result.OK()

4. Security design

4.1 JWT Filter flow (mọi request)

flowchart LR
    Req[HTTP Request] --> WL{Whitelist?}
    WL -->|Yes| Pass[Pass through]
    WL -->|No| JWT[Extract JWT\nfrom Authorization]
    JWT --> Valid{Valid token?}
    Valid -->|No| 401[401 Unauthorized]
    Valid -->|Yes| Banned{User banned?}
    Banned -->|YES: NO_ACTIVE| 403[403 Forbidden]
    Banned -->|No| Session{Session valid?\n(Redis check)}
    Session -->|No| 401
    Session -->|Yes| Continue[Continue to Controller]

4.2 Concurrent login guard

// AuthService.signIn()
lockerService.getInLockWithException(
    LockerService.LockKeyPreFix.USER_EMAIL_LOCKER,
    verifierId,  // lock key = verifierId (unique per user)
    () -> { /* signIn logic */ },
    BannedException.class
);

4.3 Single-device policy

  • Chỉ áp dụng khi x-site: APP header.
  • UserSessionRepository.createOrUpdateSession() lưu 1 session per userId trong Redis.
  • Login từ thiết bị mới → ghi đè session cũ → thiết bị cũ nhận NEW_DEVICE_CONNECTED notification qua broadcastPubSubService.

5. State machine - User status

stateDiagram-v2
    [*] --> ACTIVE: User mới tạo
    ACTIVE --> NO_ACTIVE: Admin ban user
    NO_ACTIVE --> ACTIVE: Admin unban
    ACTIVE --> DELETED: User xóa tài khoản
    DELETED --> [*]

    note right of NO_ACTIVE: Login bị chặn\nBannedException
    note right of DELETED: Login bị chặn\nUnprocessableEntityException

6. Error handling

Tình huống Exception HTTP Message
Sai login_type BadRequestException 400 INVALID_LOGIN_TYPE
User bị ban BannedException 403 YOUR_ACCOUNT_HAS_BEEN_BANNED
Sai API key Return Result.error 200 SECRET_KEY_IS_INVALID
Email không tồn tại (check-email) BadRequestException 400 EMAIL_NOT_FOUND
Wallet không tồn tại BadRequestException 400 USER_WALLET_NOT_FOUND
Code hết hạn BadRequestException 400 LOGIN_CODE_IS_EXPIRED

7. Async operations (AuthAsyncService)

Method Mô tả Blocking?
createUserWallet() Tạo wallet chính Blocking (cần trả về wallet)
createSecondaryWallet() Tạo wallet phụ (WEB3↔DM2) Non-blocking async
initializeUserSystem() Init coin + equipment Non-blocking async
sendWelcomeEmail() Email chào mừng Non-blocking async
assignNftOwner() Gán NFT ownership Non-blocking async
handleDeviceToken() Save/update firebase token Non-blocking async

8. Configuration

# authentication/src/main/resources/application.yaml
server:
  port: 8085
  tomcat:
    max-connections: 1000
    threads:
      max: 500
      min-spare: 50

application:
  authentication:
    secret-key: ${AUTH_SECRET_KEY}   # x-api-key cho sign-in + check-email
  security:
    whitelist:
      - /api/auth/generate-code
      - /api/auth/sign-in
      - /api/auth/check-email
      - /api/health-check/**

9. Điểm cần chú ý & rủi ro

# Vấn đề Ảnh hưởng Biện pháp
1 Rate limit generate-code đang bị comment out Có thể bị brute-force Re-enable @RateLimit hoặc thêm captcha
2 validateCode() bị comment out trong signIn() Auth code không được validate Cần review xem đây là intentional hay không
3 create-account-fake endpoint không có auth guard Tạo fake account tùy ý Thêm internal-only guard hoặc xóa
4 Secondary wallet creation là async, không biết thành công DM2/WEB3_AUTH user có thể thiếu wallet phụ Monitor async errors, thêm retry