A comprehensive, production-ready PostgreSQL database schema for Identity and Access Management (IAM) designed as a universal microservice for businesses of all sizes across different countries.
-
Multiple Authentication Methods
- Email and password authentication
- OAuth 2.0 integration (Google, Microsoft)
- Support for multiple OAuth providers per user
-
Email Verification
- Token-based verification with secure links
- 6-digit verification codes
- Configurable expiration periods
-
Password Management
- Secure password hashing using bcrypt
- Password reset with token and code
- Password history tracking to prevent reuse
- OAuth users can set passwords via reset flow
-
User Profile Management
- Profile updates (first name, last name, username)
- Profile image URL storage
- Optional usernames with validation
- Email change with verification
-
Account Management
- Soft delete for account removal
- Account status tracking (pending, active, suspended, deleted)
- JWT token blacklist for logout and security events
- Comprehensive audit logging
-
Security Features
- JWT-based authentication (stateless)
- JWT token blacklist for logout and revocation
- Failed login attempt logging
- Token invalidation on password reset and account deletion
- Automatic cleanup of expired data
Primary user account information
id- Unique user identifier (UUID)email- User email address (unique, validated format)username- Optional username (unique, min 3 chars)password_hash- Bcrypt hashed passwordfirst_name,last_name- User profile namesprofile_image_url- URL to profile imagestatus- Account status: pending, active, suspended, deletedemail_verified- Email verification flagcreated_at,updated_at,deleted_at- Timestampslast_login_at- Last successful login timestamp
OAuth authentication data
- Links users to external OAuth providers
- Stores access tokens and refresh tokens
- Tracks token expiration
- Unique constraint per provider per user
Multi-purpose verification system
- Email verification
- Password reset
- Email change verification
- Contains both secure tokens (for links) and codes (for manual entry)
JWT token blacklist for logout and revocation
- JWT ID (jti) storage for invalidated tokens
- Token type tracking (access, refresh)
- Reason for blacklisting
- Automatic expiration cleanup
Comprehensive audit trail
- All security-relevant actions
- JSONB metadata for flexible logging
- Resource tracking (type and ID)
- Network information (IP, user agent)
Password reuse prevention
- Historical password hash storage
- Enables password policy enforcement
auth_provider: 'email', 'google', 'microsoft'
verification_type: 'email_verification', 'password_reset', 'email_change'
user_status: 'pending', 'active', 'suspended', 'deleted'Register a new user with email and password.
Parameters:
p_email- User email addressp_password- Plain text password (will be hashed)p_first_name- Optional first namep_last_name- Optional last namep_username- Optional username
Returns:
user_id- Created user UUIDverification_token- Email verification tokenverification_code- 6-digit verification code
Example:
SELECT * FROM register_user_with_email(
'user@example.com',
'SecurePassword123!',
'John',
'Doe',
'johndoe'
);Register or authenticate a user via OAuth provider.
Parameters:
p_provider- OAuth provider (google, microsoft)p_provider_user_id- Provider's unique user IDp_email- User email from providerp_first_name- Optional first namep_last_name- Optional last namep_access_token- OAuth access tokenp_refresh_token- OAuth refresh tokenp_token_expires_at- Token expiration timestamp
Returns:
user_id- User UUID (existing or newly created)
Example:
SELECT register_user_with_oauth(
'google'::auth_provider,
'google_user_123456',
'user@gmail.com',
'Jane',
'Smith',
'ya29.a0AfH6SMB...',
'1//0gH8_K9Z...',
CURRENT_TIMESTAMP + INTERVAL '1 hour'
);Verify user email with token or code.
Parameters:
p_token- Verification token (from email link)p_code- 6-digit verification code
Returns:
BOOLEAN- Success status
Example:
-- Verify with token
SELECT verify_email('abc123...', NULL);
-- Verify with code
SELECT verify_email(NULL, '123456');Authenticate user with email and password.
Parameters:
p_email- User emailp_password- Plain text password
Returns:
user_id- User UUID (NULL on failure)success- Authentication success flagmessage- Result message
Example:
SELECT * FROM authenticate_user('user@example.com', 'password123');Request a password reset for a user.
Parameters:
p_email- User email address
Returns:
user_id- User UUIDreset_token- Password reset tokenreset_code- 6-digit reset code
Example:
SELECT * FROM request_password_reset('user@example.com');Reset password using reset token.
Parameters:
p_token- Password reset tokenp_new_password- New plain text password
Returns:
BOOLEAN- Success status
Side Effects:
- Invalidates all user sessions
- Saves old password to history
Example:
SELECT reset_password('reset_token_xyz', 'NewSecurePassword123!');Update user profile information.
Parameters:
p_user_id- User UUIDp_first_name- Optional first namep_last_name- Optional last namep_username- Optional usernamep_profile_image_url- Optional profile image URL
Returns:
BOOLEAN- Success status
Example:
SELECT update_user_profile(
'user-uuid',
p_first_name := 'John',
p_last_name := 'Updated',
p_profile_image_url := 'https://example.com/avatar.jpg'
);Retrieve complete user profile.
Parameters:
p_user_id- User UUID
Returns:
- Complete user profile with OAuth providers
Example:
SELECT * FROM get_user_profile('user-uuid');Request to change user email address.
Parameters:
p_user_id- User UUIDp_new_email- New email address
Returns:
change_token- Email change verification tokenchange_code- 6-digit verification code
Example:
SELECT * FROM request_email_change('user-uuid', 'newemail@example.com');Verify and complete email change.
Parameters:
p_token- Email change token
Returns:
BOOLEAN- Success status
Example:
SELECT verify_email_change('change_token_xyz');Soft delete a user account.
Parameters:
p_user_id- User UUID
Returns:
BOOLEAN- Success status
Side Effects:
- Sets status to 'deleted'
- Sets deleted_at timestamp
- Invalidates all sessions
- Invalidates all tokens
Example:
SELECT delete_user_account('user-uuid');Add a JWT token to the blacklist (for logout, security events).
Parameters:
p_user_id- User UUIDp_jti- JWT ID (jti claim from token)p_expires_at- Token expiration timestampp_token_type- Token type (default: 'access')p_reason- Optional reason for blacklisting
Returns:
BOOLEAN- Success status
Example:
SELECT blacklist_jwt_token(
'user-uuid',
'jwt-id-from-token',
CURRENT_TIMESTAMP + INTERVAL '15 minutes',
'access',
'user_logout'
);Check if a JWT token is blacklisted.
Parameters:
p_jti- JWT ID (jti claim from token)
Returns:
BOOLEAN- TRUE if blacklisted and not expired
Example:
SELECT is_jwt_blacklisted('jwt-id-from-token');Invalidate all tokens for a user (logout from all devices).
Parameters:
p_user_id- User UUIDp_reason- Optional reason (default: 'logout_all')
Returns:
INTEGER- Success indicator
Side Effects:
- Updates user's
updated_attimestamp - Application should check JWT
iatclaim against user'supdated_at
Example:
SELECT blacklist_all_user_tokens('user-uuid', 'security_breach');Note: For "logout all devices" functionality, your application should:
- Call this function to update the user's
updated_attimestamp - When validating JWTs, compare the token's
iat(issued at) claim with the user'supdated_at - Reject tokens where
iat < updated_at
Clean up expired tokens, sessions, and old audit logs.
Returns:
INTEGER- Count of deleted records
Cleanup Rules:
- Verification tokens: > 7 days past expiration
- JWT blacklist entries: Past expiration
- Audit logs: > 1 year old
Example:
SELECT cleanup_expired_data();- Docker and Docker Compose
- Atlas CLI (for migrations)
- pgTAP (for testing)
- Clone the repository
git clone <repository-url>
cd iam-schema- Start the database
docker-compose up -d db- Run migrations
docker-compose up migrate- Access pgAdmin (optional)
docker-compose --profile manual up pgadminNavigate to http://localhost:5050 and login with:
- Email: admin@example.com
- Password: admin
Edit .env file to customize:
# Database
PGHOST=db
POSTGRES_USER=postgres
PGPASSWORD=postgres
PGDATABASE=iam
PGPORT=5432
# pgAdmin
PGADMIN_DEFAULT_EMAIL=admin@example.com
PGADMIN_DEFAULT_PASSWORD=admin
PGADMIN_PORT=5050The project includes comprehensive pgTAP tests covering all functionality.
docker-compose --profile manual up pgtap-
Schema Tests (001_schema_tests.sql)
- Table structure validation
- Column existence and types
- Constraints and indexes
- Foreign key relationships
-
Registration Tests (002_user_registration_tests.sql)
- Email registration
- OAuth registration (Google, Microsoft)
- Duplicate handling
- Validation constraints
-
Authentication Tests (003_authentication_tests.sql)
- Valid/invalid credentials
- Unverified accounts
- Suspended/deleted accounts
- OAuth-only users
-
Email Verification Tests (004_email_verification_tests.sql)
- Token verification
- Code verification
- Expiration handling
- Duplicate usage prevention
-
Password Reset Tests (005_password_reset_tests.sql)
- Reset request
- Token validation
- Password history
- Token invalidation logging
- OAuth user password setting
-
Profile Management Tests (006_profile_management_tests.sql)
- Profile updates
- Username changes
- Profile image updates
- Data retrieval
-
Email Change Tests (007_email_change_tests.sql)
- Change request
- Verification
- Duplicate email prevention
- Token expiration
-
Account Deletion Tests (008_account_deletion_tests.sql)
- Soft delete
- Token invalidation logging
- Verification token cleanup
- Data preservation
-
JWT Management Tests (009_jwt_management_tests.sql)
- Token blacklisting
- Blacklist validation
- Expired entries handling
- Logout all devices
- Token types and reasons
-
Cleanup Tests (010_cleanup_tests.sql)
- Expired token cleanup
- JWT blacklist cleanup
- Audit log retention
This project uses Atlas for database migrations.
docker-compose --profile manual up hashdocker-compose up migrateAll SQL migration files are stored in the schema/ directory:
001_initial_schema.sql- Core tables and types002_functions.sql- Stored procedures and functionsatlas.sum- Atlas migration checksums
- Password Hashing: Bcrypt with salt factor 10
- Token Generation: Cryptographically secure random bytes (32 bytes for tokens)
- Soft Delete: Preserves data for audit and recovery
- JWT Authentication: Stateless authentication with blacklist support
- Token Revocation: Granular (per-token) and bulk (all devices) invalidation
- Audit Trail: Comprehensive logging of security events
- UUID Primary Keys: Enables distributed systems
- Indexed Columns: Performance optimization for common queries
- JSONB Metadata: Flexible audit log data without schema changes
- Soft Delete: Maintains referential integrity
- Multiple OAuth Providers: Users can link multiple authentication methods
- Optional Fields: Minimal required data (email only for OAuth)
- Extensible Audit Logs: JSONB metadata for custom tracking
- Configurable Expiration: Flexible JWT and verification token lifetimes
- Stateless Design: JWT-based auth reduces database load
- Foreign Keys with Cascade: Automatic cleanup on user deletion
- Check Constraints: Email format, username length validation
- Unique Constraints: Prevent duplicate emails, usernames, tokens
- NOT NULL Constraints: Ensure critical data is present
- Simple email/password authentication
- Optional OAuth for user convenience
- Basic profile management
- Multiple OAuth providers
- Session management across devices
- Audit logging for compliance
- Multi-region support via UUID keys
- Comprehensive audit trails
- Advanced session management
- Password policies via history
- No country-specific constraints
- UTC timestamps for global coordination
- Flexible username requirements
- Multi-provider OAuth support
This IAM system is designed to work with JWT (JSON Web Token) based authentication. Here's how to integrate it with your application:
Your JWTs should include these claims:
{
"sub": "user-uuid", // Subject (user ID)
"jti": "unique-token-id", // JWT ID (for blacklist)
"iat": 1234567890, // Issued at (Unix timestamp)
"exp": 1234571490, // Expiration (Unix timestamp)
"email": "user@example.com", // Optional: user email
"role": "user" // Optional: user role
}- Verify JWT signature and expiration (standard JWT validation)
- Check if token is blacklisted:
SELECT is_jwt_blacklisted('jti-from-token');
- Check for "logout all devices" scenario:
SELECT updated_at FROM users WHERE id = 'user-id-from-token'; -- Reject if JWT iat < user updated_at
Single Device Logout:
-- When user logs out from one device
SELECT blacklist_jwt_token(
user_id,
jti_from_token,
exp_from_token,
'access',
'user_logout'
);All Devices Logout:
-- When user clicks "logout from all devices"
SELECT blacklist_all_user_tokens(user_id, 'logout_all');
-- Then in JWT validation, reject tokens where:
-- token.iat < user.updated_atSecurity Event (Password Reset, Account Deletion):
- Password reset and account deletion automatically log
invalidate_tokens: truein audit_logs - Your application should:
- Monitor these audit log events
- Call
blacklist_all_user_tokens()or reject tokens based onupdated_at
- Blacklist Growth: Run
cleanup_expired_data()regularly (e.g., daily cron job) - Database Queries: Only 2 queries needed per request:
- Check JWT blacklist (indexed on
jti) - Get user
updated_at(primary key lookup)
- Check JWT blacklist (indexed on
- Caching: Consider caching user
updated_atwith short TTL (e.g., 5 minutes)
-- 1. Register user
SELECT * FROM register_user_with_email('user@example.com', 'password123', 'John', 'Doe');
-- Returns: user_id, verification_token, verification_code
-- 2. Send verification email with token/code
-- 3. Verify email
SELECT verify_email('token_from_email', NULL);
-- Returns: true
-- 4. User can now login
SELECT * FROM authenticate_user('user@example.com', 'password123');
-- Returns: user_id, success=true, message='Authentication successful'-- 1. Register/Login with OAuth
SELECT register_user_with_oauth(
'google'::auth_provider,
'google_id_123',
'user@gmail.com',
'John',
'Doe',
'access_token',
'refresh_token',
CURRENT_TIMESTAMP + INTERVAL '1 hour'
);
-- Returns: user_id
-- 2. Application generates JWT with user_id
-- Include 'jti' (JWT ID) claim in your JWT for blacklist support
-- Include 'iat' (issued at) claim for logout-all-devices support
-- 3. On each request, validate JWT and check blacklist
SELECT is_jwt_blacklisted('jti-from-jwt-token');
-- Returns: false (if valid)
-- 4. Compare JWT iat with user's updated_at for "logout all" support
SELECT updated_at FROM users WHERE id = 'user_id_from_jwt';
-- Reject if JWT iat < user updated_at-- Single device logout - blacklist specific token
SELECT blacklist_jwt_token(
'user-uuid',
'jti-from-token',
'token-expiration-time',
'access',
'user_logout'
);
-- Logout from all devices - invalidate all user tokens
SELECT blacklist_all_user_tokens('user-uuid', 'logout_all');
-- This updates user's updated_at timestamp
-- Application should reject JWTs where iat < updated_at-- 1. Request reset
SELECT * FROM request_password_reset('user@example.com');
-- Returns: user_id, reset_token, reset_code
-- 2. Send reset email
-- 3. Reset password
SELECT reset_password('token_from_email', 'NewPassword123!');
-- Returns: trueRun cleanup regularly (e.g., via cron job):
SELECT cleanup_expired_data();This removes:
- Verification tokens expired > 7 days ago
- Expired JWT blacklist entries
- Audit logs > 1 year old
- Regular PostgreSQL backups (pg_dump)
- Point-in-time recovery (WAL archiving)
- Encrypt backups containing user data
- Test restore procedures regularly
Key metrics to monitor:
- JWT blacklist size
- Failed login attempts
- Token expiration rates
- Audit log growth
- Query performance on indexed columns
- User updated_at changes (for logout-all tracking)
MIT License - See LICENSE file for details
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
For issues, questions, or contributions:
- GitHub Issues: [repository-url]/issues
- Documentation: This README
- Tests: See
PGTap/directory for examples