Skip to content

Authentication & JWT

Login Flow

mermaid
sequenceDiagram
    participant Client
    participant API as POST /api/auth/login
    participant Auth as AuthService
    participant DB as MySQL

    Client->>API: { "email": "admin@example.com", "password": "..." }
    API->>Auth: LoginAsync(email, password)
    Auth->>DB: SELECT * FROM users WHERE email = ?
    DB-->>Auth: user row (with passwordHash)
    Auth->>Auth: BCrypt.Verify(password, passwordHash)
    Auth->>DB: Resolve permissions for userId
    DB-->>Auth: permission rows
    Auth->>Auth: Build JWT claims
    Auth-->>API: LoginResponseDto
    API-->>Client: 200 { token, expiresAt, user }

JWT Structure

The token is signed with HS256 using the Jwt:SecretKey from appsettings.json.

Claims

ClaimTypeExampleDescription
substring"42"User ID
emailstring"admin@rx.com"User email
given_namestring"Alice"First name
family_namestring"Smith"Last name
role_idstring"1"Assigned role ID
is_adminstring"true" or "false"Admin bypass flag
permstring[]["Vendor:Read","Invoice:Create"]Resolved permission list
expintUnix timestampExpiry (default 24h)
issstring"MedRegia"Issuer
audstring"MedRegiaUsers"Audience

Admin vs Regular User

mermaid
graph TD
    LOGIN["User logs in"] --> ADMIN{"is_admin?"}
    ADMIN -->|"true"| ATOKEN["JWT with is_admin=true<br/>perm = ['*:Create']<br/>Skips ALL permission checks"]
    ADMIN -->|"false"| RTOKEN["JWT with resolved perm[] claims<br/>e.g. Vendor:Read, Invoice:Create"]

Using the Token

Include the token in every request header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Token Expiry

Tokens expire after 24 hours by default (configurable via Jwt:ExpirationHours).
There is no refresh token mechanism — users must log in again after expiry.

Configuration

json
// appsettings.json
{
  "Jwt": {
    "SecretKey": "CHANGE_ME_use_a_long_random_secret_key_at_least_32_chars",
    "Issuer": "MedRegia",
    "Audience": "MedRegiaUsers",
    "ExpirationHours": "24"
  }
}

Production Security

Always replace SecretKey with a randomly generated string of at least 32 characters before deploying to production. Never commit the real secret to source control.

Password Security

  • Passwords are hashed with BCrypt before storage — the plain text is never stored
  • The passwordHash field is never returned by any API endpoint
  • Password changes go through the UserProfile module (PUT /api/profile/password)

MedRegia — Pharmaceutical Vendor Management Platform