When to use this module

  • User needs to log inPOST /auth/login
  • Access token expires → refreshPOST /auth/refreshtoken
  • User belongs to multiple firms → switchGET /auth/setfirm
  • User logs out → invalidate sessionPOST /auth/logout

Endpoints

MethodRouteAuthDescription
POST /api/v1/auth/login ❌ None Login → JWT + refresh token
POST /api/v1/auth/refreshtoken ❌ None Exchange refresh token for new JWT
GET /api/v1/auth/setfirm 🔒 Bearer Switch active firm context
POST /api/v1/auth/logout 🔒 Bearer Invalidate current session

Authentication Flow

// 1. Login request Client POST /auth/login { email, password } → Server validates credentials (PBKDF2 hash check) → Server loads user's firm relations → Server generates JWT with claims: UserId, Email, FirmId, RelationId → Server creates refresh token in FirmUserRefreshToken table → Returns { token, refreshToken, firms[] } // 2. API calls Client calls API with Authorization: Bearer <token> // 3. Token refresh (when expired) Client POST /auth/refreshtoken { refreshToken } → Server validates token ↔ DB record → Returns new { token, refreshToken } // 4. Multi-firm switch (optional) GET /auth/setfirm?userId=&relationId=&sessionId= → Returns new JWT with different FirmId claim
POST

/api/v1/auth/login

Authenticates a user and returns a JWT access token, refresh token, and the list of firms the user belongs to.

This endpoint does not require Authorization header.

Request Body

JSON
{
  "email": "admin@company.com",
  "password": "SecurePass123!"
}

Request Parameters

FieldTypeRequiredDescription
emailstringrequiredUser's email address
passwordstringrequiredUser's password (min 8 chars, complexity required)

Response — 200 OK

JSON
{
  "isSuccess": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "d4f8e2a1-9b3c-4e5f-a6d7-8b9c0d1e2f3a",
    "expiresIn": 3600,
    "user": {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "firstName": "John",
      "lastName": "Doe",
      "email": "admin@company.com",
      "userName": "johndoe"
    },
    "firms": [
      {
        "firmId": "f1e2d3c4-b5a6-9870-fedc-ba9876543210",
        "relationId": "r1e2d3c4-b5a6-9870-fedc-ba9876543210",
        "firmName": "Acme Corp",
        "sessionId": "s1e2d3c4-b5a6-9870-fedc-ba9876543210"
      }
    ]
  },
  "message": "Login successful",
  "error": null
}

JWT Claims

ClaimValue
NameIdentifierUserId (Guid)
EmailUser email
FirmIdActive firm ID (Guid)
RelationIdFirmUserRelation ID (Guid)

Error Responses

StatusErrorCause
400ValidationErrorEmail/password missing or malformed
401UnauthorizedInvalid credentials or inactive account
POST

/api/v1/auth/refreshtoken

Exchange an expired JWT for a new access token + refresh token pair. The old refresh token is invalidated.

Request Body

JSON
{
  "refreshToken": "d4f8e2a1-9b3c-4e5f-a6d7-8b9c0d1e2f3a"
}

Response — 200 OK

JSON
{
  "isSuccess": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "new-refresh-token-guid",
    "expiresIn": 3600
  },
  "message": "Token refreshed successfully"
}
GET

/api/v1/auth/setfirm

Switches the user's active firm context. Returns a new JWT with the selected firm's ID embedded. Use when a user belongs to multiple firms.

Requires Authorization: Bearer <token> header. Returns a new token — replace the old one in client storage.

Query Parameters

ParameterTypeRequiredDescription
userIdGuidrequiredThe user ID (from login response)
relationIdGuidrequiredThe FirmUserRelation ID for the target firm
sessionIdGuidrequiredCurrent session ID (from login response)

Example Request

HTTP
GET /api/v1/auth/setfirm?userId=a1b2c3d4&relationId=r1e2d3c4&sessionId=s1e2d3c4
Authorization: Bearer <current_token>
POST

/api/v1/auth/logout

Invalidates the current session and refresh token. Subsequent requests with the same refresh token will fail.

After logout, the client should discard both the access token and refresh token from storage.

Response — 200 OK

JSON
{
  "isSuccess": true,
  "data": null,
  "message": "Logged out successfully"
}