This document provides detailed examples of API flows with request/response examples.
- User Signup Flow
- User Signin Flow
- Email Verification Flow
- Password Reset Flow
- Protected Resource Access
- Role Assignment Flow
- Permission Computation Flow
- Audit Log Query Flow
POST http://localhost:42069/api/v1/auth/signup
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe"
}-
Controller Validation (
auth_controller.go:Signup())- Validates JSON binding
- Checks required fields
-
Service Layer (
auth_service.go:Signup())- Hashes password with bcrypt (cost 10)
- Creates user in database
- Assigns default role (from
roles.is_default = true) - Generates verification token (UUID)
- Creates
email_verificationsrecord (expires in 24h)
-
Email Service (
email_service.go:SendVerificationEmail())- Builds HTML email template
- Sends via MailhogProvider (dev) or SESProvider (prod)
- Logs to
email_logstable
{
"status": "success",
"message": "User created successfully. Please check your email to verify your account.",
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"is_verified": false
}
}Subject: Verify your email address
Hello John,
Welcome to Go-Auth! Please verify your email address by clicking the link below:
http://localhost:3000/verify-email?token=abcd1234-5678-90ef-ghij-klmnopqrstuv
This link expires in 24 hours.
---
Go-Auth Team
POST http://localhost:42069/api/v1/auth/signin
Content-Type: application/json
{
"email": "john.doe@example.com",
"password": "SecurePass123!"
}-
Controller Validation (
auth_controller.go:Signin())- Validates JSON binding
-
Service Layer (
auth_service.go:Signin())- Finds user by email
- Compares password hash using bcrypt
- Checks
is_active = trueandis_verified = true - Generates JWT token:
Claims: - user_id: UUID - email: string - exp: now + 24h - iat: now Signature: RS256 with private key
- Stores session in Redis:
Key: "session:{user_id}" Value: token TTL: 24 hours - Updates
users.last_logintimestamp
{
"status": "success",
"message": "Authentication successful",
"data": {
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleTEifQ...",
"user": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_verified": true,
"last_login": "2025-10-19T10:30:00Z"
}
}
}Client stores the token in:
- Browser: localStorage or httpOnly cookie
- Mobile: Secure storage (Keychain/Keystore)
GET http://localhost:3000/verify-email?token=abcd1234-5678-90ef-ghij-klmnopqrstuvFrontend extracts token and calls backend:
GET http://localhost:42069/api/v1/auth/verify-email?token=abcd1234-5678-90ef-ghij-klmnopqrstuv-
Controller (
auth_controller.go:VerifyEmail())- Extracts token from query param
-
Service Layer (
auth_service.go:VerifyEmail())- Finds
email_verificationsrecord by token - Checks token not expired (
expires_at > now) - Checks token not already used (
verified_at IS NULL) - Updates
users.is_verified = true - Updates
email_verifications.verified_at = now
- Finds
{
"status": "success",
"message": "Email verified successfully",
"data": null
}Token Expired:
{
"status": "failure",
"message": "Verification failed",
"error": {
"error_code": "TOKEN_EXPIRED",
"error_msg": "Verification token has expired"
}
}Token Already Used:
{
"status": "failure",
"message": "Verification failed",
"error": {
"error_code": "TOKEN_USED",
"error_msg": "Email already verified"
}
}POST http://localhost:42069/api/v1/auth/forgot-password
Content-Type: application/json
{
"email": "john.doe@example.com"
}- Service Layer (
auth_service.go:ForgotPassword())- Finds user by email (fails silently if not found for security)
- Generates reset token (UUID)
- Creates
password_resetsrecord (expires in 1 hour) - Sends reset email
{
"status": "success",
"message": "If that email exists, a password reset link has been sent",
"data": null
}Subject: Reset your password
Hello John,
We received a request to reset your password. Click the link below:
http://localhost:3000/reset-password?token=xyz789-abcd-1234-efgh-567890ijklmn
This link expires in 1 hour. If you didn't request this, ignore this email.
---
Go-Auth Team
POST http://localhost:42069/api/v1/auth/reset-password
Content-Type: application/json
{
"token": "xyz789-abcd-1234-efgh-567890ijklmn",
"new_password": "NewSecurePass456!"
}- Service Layer (
auth_service.go:ResetPassword())- Finds
password_resetsrecord by token - Checks token not expired (
expires_at > now) - Checks token not already used (
used_at IS NULL) - Hashes new password
- Updates
users.password_hash - Updates
password_resets.used_at = now - Invalidates all user sessions in Redis
- Finds
{
"status": "success",
"message": "Password reset successfully. Please sign in with your new password.",
"data": null
}GET http://localhost:42069/api/v1/auth/me
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleTEifQ...-
RequireAuth Middleware (
middleware/auth.go:RequireAuth())a. Extract token from
Authorization: Bearer <token>headerb. Parse JWT header to get
kid(key ID)c. Fetch public key from Redis:
key := fmt.Sprintf("auth:jwks:key:%s", kid) publicKey, err := redis.Get(ctx, key).Result()
d. Verify JWT signature using public key:
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return publicKey, nil })
e. Validate claims:
- Check
exp(expiration) - Extract
user_id
f. Check session exists in Redis:
sessionKey := fmt.Sprintf("session:%s", userID) exists, err := redis.Exists(ctx, sessionKey).Result()
g. Store
user_idin Gin context:c.Set(middleware.UserIDKey, userID) c.Next()
- Check
func (c *AuthController) GetUserInfo(ctx *gin.Context) {
// Get user_id from context (set by middleware)
userID, _ := ctx.Get(middleware.UserIDKey)
userUUID := userID.(uuid.UUID)
// Query database
user, err := c.service.GetUserInfo(ctx.Request.Context(), userUUID)
// Return response
utils.RespondSuccess(ctx, types.HTTP.Ok, "User info retrieved", user)
}{
"status": "success",
"message": "User info retrieved",
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_verified": true,
"last_login": "2025-10-19T10:30:00Z",
"created_at": "2025-10-15T08:00:00Z"
}
}Missing Token:
{
"status": "failure",
"message": "Authentication required",
"error": {
"error_code": "UNAUTHORIZED",
"error_msg": "Missing or invalid authorization header"
}
}Invalid Token:
{
"status": "failure",
"message": "Authentication failed",
"error": {
"error_code": "INVALID_TOKEN",
"error_msg": "Token signature is invalid"
}
}Expired Token:
{
"status": "failure",
"message": "Authentication failed",
"error": {
"error_code": "TOKEN_EXPIRED",
"error_msg": "Token has expired"
}
}Session Not Found:
{
"status": "failure",
"message": "Session invalid",
"error": {
"error_code": "SESSION_NOT_FOUND",
"error_msg": "Please sign in again"
}
}POST http://localhost:42069/api/v1/rbac/users/assign-role
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"role_id": 2
}-
Middleware (
middleware/auth.go:RequireAuth())- Validates admin's JWT
- Sets
admin_idin context
-
Controller (
rbac_controller.go:AssignRole())- Validates request body
- Extracts
actor_id(admin_id) from context - Calls
RBACService.AssignRole()
-
Service Layer (
rbac_service.go:AssignRole())a. Check user exists:
exists, err := client.Users.Query(). Where(users.IDEQ(userID)). Exist(ctx)
b. Check role exists and get details:
role, err := client.Roles.Get(ctx, roleID)
c. Check
max_usersconstraint:if role.MaxUsers != nil { count, err := client.UserRoles.Query(). Where(userroles.RoleIDEQ(roleID)). Count(ctx) if count >= *role.MaxUsers { return fmt.Errorf("role has reached maximum users limit") } }
d. Check not already assigned:
exists, err := client.UserRoles.Query(). Where( userroles.UserIDEQ(userID), userroles.RoleIDEQ(roleID), ). Exist(ctx) if exists { return fmt.Errorf("role already assigned") }
e. Create assignment:
_, err = client.UserRoles.Create(). SetUserID(userID). SetRoleID(roleID). SetNillableAssignedBy(&actorID). Save(ctx)
f. Create audit log:
client.AuditLogs.Create(). SetActorID(actorID). SetActionType("role.assign"). SetResourceType("user_role"). SetResourceID(userID.String()). SetMetadata(map[string]interface{}{ "user_id": userID.String(), "role_id": roleID, }). Save(ctx)
{
"status": "success",
"message": "Role assigned successfully",
"data": null
}GET http://localhost:42069/api/v1/rbac/users/550e8400-e29b-41d4-a716-446655440000/roles
Authorization: Bearer <token>Response:
{
"status": "success",
"message": "User roles retrieved successfully",
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.doe@example.com",
"roles": [
{
"id": 1,
"code": "user",
"name": "User",
"description": "Default user role",
"is_system": false,
"is_default": true
},
{
"id": 2,
"code": "admin",
"name": "Administrator",
"description": "Admin role with elevated privileges",
"is_system": true,
"is_default": false
}
],
"assigned_at": "2025-10-19T11:00:00Z"
}
}GET http://localhost:42069/api/v1/rbac/users/550e8400-e29b-41d4-a716-446655440000/permissions
Authorization: Bearer <token>-
Service Layer (
rbac_service.go:GetUserPermissions())a. Get all user roles:
userRolesList, err := client.UserRoles.Query(). Where(userroles.UserIDEQ(userID)). All(ctx) roleIDs := []int{} // [1, 2] for _, ur := range userRolesList { roleIDs = append(roleIDs, ur.RoleID) }
b. Get all permissions for these roles:
rolePerms, err := client.RolePermissions.Query(). Where(rolepermissions.RoleIDIn(roleIDs...)). WithPermission(). All(ctx)
c. Deduplicate permissions:
permMap := make(map[int]*ent.Permissions) for _, rp := range rolePerms { if rp.Edges.Permission != nil { permMap[rp.PermissionID] = rp.Edges.Permission } }
d. Convert to response format
{
"status": "success",
"message": "User permissions retrieved successfully",
"data": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"permissions": [
{
"id": 10,
"code": "users.read",
"name": "View Users",
"description": "Can view user information",
"resource": "users",
"action": "read"
},
{
"id": 11,
"code": "users.write",
"name": "Manage Users",
"description": "Can create and update users",
"resource": "users",
"action": "write"
},
{
"id": 20,
"code": "rbac.read",
"name": "View RBAC",
"description": "Can view roles and permissions",
"resource": "rbac",
"action": "read"
},
{
"id": 21,
"code": "rbac.write",
"name": "Manage RBAC",
"description": "Can assign roles and modify permissions",
"resource": "rbac",
"action": "write"
}
]
}
}User: john.doe@example.com
↓
Roles:
1. user (is_default: true)
- users.read.self
- users.write.self
2. admin (assigned by super-admin)
- users.* (wildcard: users.read, users.write, users.delete)
- rbac.* (wildcard: rbac.read, rbac.write)
↓
Computed Permissions (deduplicated):
- users.read.self
- users.write.self
- users.read
- users.write
- users.delete
- rbac.read
- rbac.write
GET http://localhost:42069/api/v1/rbac/audit-logs?actor_id=123e4567-e89b-12d3-a456-426614174000&action_type=role.assign&limit=10&offset=0
Authorization: Bearer <admin_token>-
Controller (
rbac_controller.go:GetAuditLogs())- Binds query parameters to
AuditLogFilterstruct - Calls
RBACService.GetAuditLogs()
- Binds query parameters to
-
Service Layer (
rbac_service.go:GetAuditLogs())a. Build query with filters:
query := client.AuditLogs.Query() if filter.ActorID != "" { actorUUID, _ := uuid.Parse(filter.ActorID) query = query.Where(auditlogs.ActorIDEQ(actorUUID)) } if filter.ActionType != "" { query = query.Where(auditlogs.ActionTypeEQ(filter.ActionType)) } if filter.ResourceType != "" { query = query.Where(auditlogs.ResourceTypeEQ(filter.ResourceType)) }
b. Apply pagination:
if filter.Limit == 0 { filter.Limit = 50 } if filter.Limit > 100 { filter.Limit = 100 } logs, err := query. Limit(filter.Limit). Offset(filter.Offset). Order(ent.Desc(auditlogs.FieldCreatedAt)). All(ctx)
{
"status": "success",
"message": "Audit logs retrieved successfully",
"data": [
{
"id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
"actor_id": "123e4567-e89b-12d3-a456-426614174000",
"action_type": "role.assign",
"resource_type": "user_role",
"resource_id": "550e8400-e29b-41d4-a716-446655440000",
"metadata": {
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"role_id": 2
},
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2025-10-19T11:00:00Z"
},
{
"id": "b2c3d4e5-f6a7-5890-b123-456789abcdef",
"actor_id": "123e4567-e89b-12d3-a456-426614174000",
"action_type": "role.assign",
"resource_type": "user_role",
"resource_id": "660f9511-f30c-52e5-b827-557766551111",
"metadata": {
"user_id": "660f9511-f30c-52e5-b827-557766551111",
"role_id": 3
},
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2025-10-19T10:45:00Z"
}
]
}role.assign- Role assigned to userrole.remove- Role removed from userrole.permissions.update- Role permissions modifieduser.create- User account createduser.update- User profile updateduser.delete- User account deleted
This document covers the major API flows in Go-Auth:
- Signup: User creation, role assignment, email verification
- Signin: Authentication, JWT generation, session storage
- Email Verification: Token-based email confirmation
- Password Reset: Secure password recovery flow
- Protected Access: JWT validation, session checking
- Role Assignment: RBAC operations with audit logging
- Permission Computation: Effective permissions from multiple roles
- Audit Logs: Querying RBAC change history
All flows follow the standard ApiResponse format with proper error handling and security validations.