Files
pos-system/docs/en/guides/iam-authentication.md
Ho Ngoc Hai 928a22fe3e feat(authentication): Implement email verification, two-factor authentication, and social login features
- Added endpoints for sending and confirming email verification, enhancing user account security.
- Integrated two-factor authentication (2FA) with TOTP support, including enabling, verifying, and disabling 2FA.
- Implemented social login functionality for Google and Facebook, allowing users to authenticate using their existing accounts.
- Updated dependency injection to include services for email, 2FA, and social login.
- Enhanced documentation to reflect new features and usage examples for email verification and 2FA.
2026-01-12 23:07:53 +07:00

9.4 KiB

IAM Service Authentication Guide

Complete guide for authentication using GoodGo IAM Service with OAuth2/OpenID Connect.

Overview

The IAM Service provides OAuth2/OIDC authentication using Duende IdentityServer:

  • Password Grant - User login with email/password
  • Refresh Token - Token renewal without re-authentication
  • Client Credentials - Service-to-service authentication
  • Email Verification - SMTP-based email confirmation
  • Two-Factor Authentication (2FA) - TOTP with QR code and recovery codes
  • Social Login - Google and Facebook OAuth integration

Quick Start

1. Register a User

curl -X POST http://localhost:5001/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "Password123!",
    "firstName": "John",
    "lastName": "Doe"
  }'

2. Login (Get Tokens)

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=user@example.com" \
  -d "password=Password123!" \
  -d "scope=openid profile email offline_access"

Response:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "eyJhbGciOiJSUzI1NiIs...",
  "scope": "openid profile email offline_access"
}

3. Use Access Token

curl http://localhost:5001/api/v1/users/me \
  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..."

OAuth2 Grant Types

Password Grant (User Login)

For trusted first-party applications where users enter credentials directly.

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password" \
  -d "username=user@example.com" \
  -d "password=Password123!" \
  -d "scope=openid profile email offline_access"
Parameter Required Description
grant_type Yes Must be password
username Yes User's email
password Yes User's password
scope No Space-separated scopes

Refresh Token

Renew access token without re-entering credentials.

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=eyJhbGciOiJSUzI1NiIs..."
Parameter Required Description
grant_type Yes Must be refresh_token
refresh_token Yes Valid refresh token

Client Credentials (Service-to-Service)

For backend services authenticating without user context.

curl -X POST http://localhost:5001/connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=goodgo-service" \
  -d "client_secret=service-secret" \
  -d "scope=api"
Parameter Required Description
grant_type Yes Must be client_credentials
client_id Yes Registered client ID
client_secret Yes Client secret
scope No Requested scopes

Note: Client applications must be registered in the database before use.

Token Lifetimes

Token Type Lifetime Use
Access Token 15 minutes API authorization
Refresh Token 7 days Token renewal

Available Scopes

Scope Description
openid Required for OIDC
profile User profile (name)
email User email
roles User roles
offline_access Get refresh token
api API access

API Endpoints

Authentication

Method Endpoint Description
POST /api/v1/auth/register Register new user
POST /connect/token OAuth2 token endpoint
POST /api/v1/auth/change-password Change password (auth required)
POST /api/v1/auth/logout Revoke tokens (auth required)

Email Verification

Method Endpoint Description
POST /api/v1/auth/send-verification-email Send email verification link (auth required)
POST /api/v1/auth/confirm-email Confirm email with token

Two-Factor Authentication (2FA)

Method Endpoint Description
POST /api/v1/auth/2fa/enable Enable 2FA (get QR code) (auth required)
POST /api/v1/auth/2fa/verify Verify TOTP code & activate (auth required)
POST /api/v1/auth/2fa/disable Disable 2FA (auth required)

Social Login

Method Endpoint Description
GET /api/v1/auth/external-login/{provider} Initiate OAuth flow (Google/Facebook)
GET /api/v1/auth/external-callback Handle OAuth callback
GET /api/v1/auth/linked-accounts Get linked OAuth providers (auth required)

User Management

Method Endpoint Description
GET /api/v1/users List users (paginated)
GET /api/v1/users/me Current user info
GET /api/v1/users/{id} Get user by ID
PUT /api/v1/users/{id} Update user
DELETE /api/v1/users/{id} Delete user (soft)

Integration Examples

JavaScript/TypeScript

// Login
const response = await fetch('http://localhost:5001/connect/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'password',
    username: 'user@example.com',
    password: 'Password123!',
    scope: 'openid profile email offline_access'
  })
});
const { access_token, refresh_token } = await response.json();

// Use token
const user = await fetch('http://localhost:5001/api/v1/users/me', {
  headers: { 'Authorization': `Bearer ${access_token}` }
}).then(r => r.json());

C# / .NET

// Login
var client = new HttpClient();
var content = new FormUrlEncodedContent(new Dictionary<string, string>
{
    ["grant_type"] = "password",
    ["username"] = "user@example.com",
    ["password"] = "Password123!",
    ["scope"] = "openid profile email offline_access"
});
var response = await client.PostAsync("http://localhost:5001/connect/token", content);
var tokens = await response.Content.ReadFromJsonAsync<TokenResponse>();

// Use token
client.DefaultRequestHeaders.Authorization = 
    new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
var user = await client.GetFromJsonAsync<UserDto>("/api/v1/users/me");

Email Verification

Send Verification Email

curl -X POST http://localhost:5001/api/v1/auth/send-verification-email \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com"}'

Confirm Email

curl -X POST http://localhost:5001/api/v1/auth/confirm-email \
  -H "Content-Type: application/json" \
  -d '{"userId": "user-guid", "token": "confirmation-token"}'

Two-Factor Authentication (2FA)

Enable 2FA

curl -X POST http://localhost:5001/api/v1/auth/2fa/enable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
  "success": true,
  "data": {
    "secretKey": "JBSWY3DPEHPK3PXP",
    "qrCodeBase64": "data:image/png;base64,...",
    "recoveryCodes": ["code1", "code2", "code3"]
  }
}

Verify 2FA Code

curl -X POST http://localhost:5001/api/v1/auth/2fa/verify \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'

Disable 2FA

curl -X POST http://localhost:5001/api/v1/auth/2fa/disable \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'

Social Login

Initiate OAuth Flow

Redirect user to:

GET http://localhost:5001/api/v1/auth/external-login/Google?returnUrl=http://your-app/callback
GET http://localhost:5001/api/v1/auth/external-login/Facebook?returnUrl=http://your-app/callback

Get Linked Accounts

curl http://localhost:5001/api/v1/auth/linked-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
  "success": true,
  "data": {
    "linkedProviders": [
      {"provider": "Google", "providerDisplayName": "Google"},
      {"provider": "Facebook", "providerDisplayName": "Facebook"}
    ]
  }
}

Error Responses

Common Errors

Error HTTP Code Description
invalid_grant 400 Invalid credentials
invalid_client 400 Unknown client_id
invalid_scope 400 Invalid scope requested
unauthorized 401 Invalid/expired token

Error Response Format

{
  "error": "invalid_grant",
  "error_description": "The username or password is incorrect."
}

Security Best Practices

  1. Store tokens securely - Use httpOnly cookies or secure storage
  2. Refresh tokens proactively - Before access token expires
  3. Use HTTPS in production - Never send credentials over HTTP
  4. Logout properly - Call logout endpoint to revoke tokens
  5. Validate tokens server-side - Don't trust client-side validation

Swagger UI

Access interactive API documentation: