Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ti-infinite/GSMApplication/llms.txt

Use this file to discover all available pages before exploring further.

GSMAuth is the dedicated authentication microservice for the GSM Application platform. It is the only service that knows how to map a company identifier to a database connection string, validate user credentials against the tenant’s Users table, and issue signed JWTs carrying the tenant and profile identity claims that every other service relies on. All login traffic enters through the gateway’s anonymous /api/security/** route and is forwarded to GSMAuth without requiring a prior token.

Layer Structure

GSMAuth is organized into seven projects following a strict layered architecture:

GSMAuth.Api

HTTP controllers, Swagger, and request/response pipeline. Hosts AuthController and TenantController.

GSMAuth.Business

Login orchestration logic: credential validation, JWT generation, and password hash verification.

GSMAuth.Abstractions

Interfaces (IAuthService, ITenantConnectionResolver, ITenantConfigurationService) that decouple layers via DIP.

GSMAuth.Entities

DTOs (LoginRequestDto, LoginDto, AuthenticatedUserDto, TenantResolveDto) and shared value objects.

GSMAuth.Tenant

Transversal TenantContext and tenant middleware. Contains no business logic.

GSMAuth.Infrastructure

PBKDF2 password hashing, JWT token generation, and TenantConnectionResolver implementation.

GSMAuth.DataAccess

EF Core contexts: RegistryDbContext (reads TenantRegistryDb.Tenants) and the dynamic per-tenant DbContext.

Login Flow

Authentication follows a seven-step process that crosses from the HTTP layer down to the tenant database and back.
1

Client posts credentials

The caller sends a POST /api/v1/auth/login request with the following body:
{
  "IDCompany": "IH001",
  "User":      "jsmith",
  "Password":  "MyS3cretP@ss"
}
All three fields are required. A missing field returns 400 Bad Request.
2

Lookup tenant in the registry

TenantConnectionResolver queries TenantRegistryDb.Tenants where CompanyId = IDCompany AND IsActive = 1. If no active row is found, the service returns 404 Not Found without touching any tenant database.
// TenantConnectionResolver.cs
var tenantRecord = await _registryDbContext.Tenants
    .AsNoTracking()
    .FirstOrDefaultAsync(
        x => x.CompanyId == companyId && x.IsActive,
        cancellationToken);
3

Build dynamic tenant connection

The Server, Database, DbUser, and DbPassword columns from the registry row are assembled into a connection string, and a scoped DbContext targeting the tenant database is instantiated for this request.
4

Query the Users table

The tenant-specific DbContext queries the Users table for a record matching the submitted username.
5

Verify password and check account status

The service checks that user.IsActive is true — an inactive account returns 401 Unauthorized. It then calls PasswordHasher.Verify(password, user.PasswordHash) using ASP.NET Core Identity’s PasswordHasher<T>. A hash mismatch also returns 401 Unauthorized.
6

Issue JWT

A signed JWT is generated containing three key claims:
ClaimSource
subUser identifier (GUID)
companyIdIDCompany from the login request
idProfileInteger profile ID from the Users table
The token is signed with the secret configured via JwtSettings__SecretKey and bounded by JwtSettings__Issuer, JwtSettings__Audience, and JwtSettings__ExpirationMinutes.
7

Return token and set cookie

The controller appends the raw JWT to the LoginDto response body and simultaneously sets the gsm_token HttpOnly cookie:
Response.Cookies.Append("gsm_token", response.Data.Token, new CookieOptions
{
    HttpOnly = true,
    SameSite = SameSiteMode.Strict,
    Expires  = response.Data.ExpiresAtUtc,
    Path     = "/"
});
The response body shape is:
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expiresAtUtc": "2025-07-01T12:00:00Z",
    "user": {
      "idUser":                "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "username":              "jsmith",
      "fullName":              "John Smith",
      "email":                 "jsmith@example.com",
      "idProfile":             2,
      "passwordChangeRequired": false,
      "location":              "Farm A",
      "department":            "Operations"
    }
  }
}

Endpoints

POST /api/v1/auth/login

Authenticates a user against the tenant database and returns a JWT.
IDCompany
string
required
The company identifier matching a CompanyId value in TenantRegistryDb.Tenants.
User
string
required
The username as stored in the tenant’s Users table.
Password
string
required
The plaintext password. It is never stored — it is hashed on the fly and compared to PasswordHash.
Responses:
StatusMeaning
200 OKCredentials valid; body contains LoginDto and gsm_token cookie is set.
400 Bad RequestOne or more required fields are missing or malformed.
401 UnauthorizedTenant not found or inactive, user not found, user is inactive, or password does not match.
500 Internal Server ErrorUnexpected error during authentication.

POST /api/v1/auth/logout

Requires a valid Bearer token. Deletes the gsm_token cookie by setting its expiry in the past:
Response.Cookies.Delete("gsm_token", new CookieOptions { Path = "/" });

POST /api/v1/tenant/resolve

Anonymous endpoint used by the frontend to verify that a company identifier exists and to fetch its theming configuration before the user enters credentials.
IDCompany
string
required
The company identifier to resolve.
Response body:
tenantExists
boolean
true if an active tenant was found for the given IDCompany.
jsonStyles
string | null
The raw JsonStyles JSON string stored in Tenants.JsonStyles. Parse this on the client to extract the light, dark, and meta theme properties. See Tenant Theming.

JWT Claims Reference

ClaimTypeDescription
substring (GUID)Unique user identifier
companyIdstringTenant identifier; injected into X-Company-Id by the gateway
idProfileintegerUser’s permission profile; injected into X-Profile-Id by the gateway
PropertyValue
Namegsm_token
HttpOnlytrue — not accessible from JavaScript
SameSiteStrict — not sent on cross-site requests
Path/
ExpiresMatches the JWT ExpiresAtUtc value

Password Storage

Passwords are never stored in plaintext. The Users table (db_ms.Users) stores a single PasswordHash column that holds the output produced by ASP.NET Core Identity’s PasswordHasher<T>. The salt is embedded within the hash string itself — no separate PasswordSalt column is required or used.
The PasswordHash column must be populated using ASP.NET Core Identity’s PasswordHasher<T>. Raw PBKDF2 hashes or hashes generated by other algorithms will fail verification. If PasswordHash is null or empty for a user row, that user will be unable to authenticate.

Environment Variables

DB_MASTER_URL
string
required
Full connection string to TenantRegistryDb. This variable is mandatory — the service throws InvalidOperationException at startup if it is absent. There is no appsettings.json fallback; the value must be supplied via the environment.
JWT_SECRET
string
required
Secret key used to sign and verify JWT tokens. This variable is mandatory — the service throws InvalidOperationException at startup if it is absent. The value is injected into JwtSettings:SecretKey at runtime and is never read from appsettings.json.
JwtSettings__Issuer
string
JWT issuer claim. Must match the Issuer configured in the gateway. Defaults to GSMAuth.
JwtSettings__Audience
string
JWT audience claim. Must match the Audience configured in the gateway. Defaults to GSMClients.
JwtSettings__ExpirationMinutes
integer
Token lifetime in minutes. Tokens expire absolutely at now + ExpirationMinutes; there is no refresh mechanism.

Build docs developers (and LLMs) love