Skip to content

Troubleshooting: Authentication and Authorization

Authentication is handled in three places:

  • client-service handles login and token refresh;
  • gateway-service checks access tokens and Redis sessions;
  • each service checks the admin API key for internal requests.

Start by finding which service returned the error.

Login or authenticated request returns 401

Error What to check
Invalid credentials The username exists, the password is correct, and the user status is ACTIVE.
Missing or invalid Authorization header The request has a valid Bearer <token> header.
Invalid or expired token The token has not expired, and client-service and gateway-service use the same JWT_SECRET_KEY.
Session not found The access-token session exists in the Redis database used by both services.
Invalid session payload The Redis session contains valid session JSON.

Check the user

Use the following query to check the user and client status:

SELECT cu.id AS user_id,
       cu.client_id,
       cu.username,
       cu.status AS user_status,
       c.status AS client_status,
       (cu.password LIKE '$2%') AS password_looks_bcrypt,
       cu.updated_date AS user_updated_at
FROM client_service.client_users cu
JOIN client_service.clients c ON c.id = cu.client_id
WHERE cu.username = :username;

Check the result:

  • No row means the username does not exist.
  • The user must have the ACTIVE status.
  • password_looks_bcrypt should be true.
  • To reset the password, call PUT /admin/clients/{clientId}/users/{userId} with the X-Admin-API-Key header:
{
  "password": "new-password"
}

The password must be between 8 and 255 characters. The API hashes it before saving it. Do not read or edit the password hash directly. Resetting the password does not revoke existing sessions.

Check the access token and session

The login flow is:

POST /auth/login
  -> client_users lookup
  -> BCrypt password check + user ACTIVE check
  -> client-service signs HS512 JWT with JWT_SECRET_KEY
  -> client-service writes sms:token:{raw JWT} to Redis
  -> caller sends Bearer JWT to gateway
  -> gateway verifies HS512 signature/expiry
  -> gateway reads the same sms:token:{raw JWT}
  -> gateway injects X-Client-Id, X-User-Id, X-Username and X-Status

Check the following without printing secrets or tokens:

  1. Client-service and gateway-service use the same JWT_SECRET_KEY. Compare the secret version or checksum, not its value.
  2. Both services use the same Redis host, port, credentials, and database (REDIS_DATABASE_INDEX, default 5).
  3. Check EXISTS and TTL for sms:token:<access-token> in Redis.
  4. Confirm that both services can connect to Redis.

The default access-token lifetime is 24 hours. Logout removes the Redis session, so the token stops working even if it has not expired.

Never add an access token to logs, tickets, terminal transcripts, or monitoring labels.

Inactive users and existing sessions

Change the user status through PUT /admin/clients/{clientId}/users/{userId} with the X-Admin-API-Key header:

{
  "status": "INACTIVE"
}

Changing a user to inactive does not remove an existing Redis session. An inactive user cannot log in or refresh a token, but an existing access token continues to work until its Redis session is removed or expires.

If the access and refresh tokens are available, revoke both through the logout API:

POST /auth/logout
Authorization: Bearer <access-token>
Content-Type: application/json

{
  "refreshToken": "<refresh-token>"
}

For urgent operator revocation when the tokens are not available, delete the user's matching session keys from Redis:

  • access session: sms:token:<access-token>;
  • refresh session: sms:refresh:<sha256(refresh-token)>.

There is currently no admin API to revoke all sessions by user ID. Use SCAN on both key prefixes, check the stored session JSON for the target userId, and delete only the matching keys. Use the Redis database configured by REDIS_DATABASE_INDEX (default 5). Do not use KEYS in production or expose token values in logs and tickets.

Refresh request fails on /auth/refresh

Refresh tokens can be used only once. Redis stores the token hash at sms:refresh:{sha256(raw refresh token)}. It does not store the raw token.

Common causes:

  • The token was already used by another tab or request.
  • The token passed its default seven-day lifetime.
  • The client reused the old token instead of saving the new token pair.
  • Client-service is using the wrong Redis instance or database.
  • The user was deleted or made inactive.

Do not restore a used refresh token. Check the related request and response logs. If the result is unclear, ask the user to log in again.

Never log a raw refresh token. If a token check is required, calculate its SHA-256 locally and check the matching key in Redis database 5.

Service-to-service call fails with 403

Internal requests use the X-Admin-API-Key header. The receiving service returns 403 when the header is missing or its value does not match.

  1. Use logs and trace IDs to find which service returned 403.
  2. Confirm that the caller sends the X-Admin-API-Key header.
  3. Compare the ADMIN_API_KEY secret version or checksum on the caller and receiver. Do not print the value.
  4. Check for extra spaces or line breaks in the configured secret.
  5. Confirm that the request reached the expected service instance.
  6. Redeploy the affected service after fixing the configuration.

Client-service requires an admin key of at least 32 characters. The caller and receiver must use the same value.