Skip to content

Authentication & Authorization Architecture - ThriveSend B2B2G

Authentication & Authorization Architecture - ThriveSend B2B2G

Section titled “Authentication & Authorization Architecture - ThriveSend B2B2G”

Version: 0.1.4 Last Updated: November 5, 2025 Status: Production Auth Provider: Clerk Security Standard: POPIA + Government Grade


ThriveSend B2B2G implements a comprehensive authentication and authorization system using Clerk for identity management, combined with custom role-based access control (RBAC) and security clearance levels for government compliance.


sequenceDiagram
participant User as User Browser
participant App as Next.js App
participant ClerkJS as Clerk Frontend SDK
participant ClerkAPI as Clerk API
participant MW as Next.js Middleware
participant DB as PostgreSQL
User->>App: Navigate to Protected Route
App->>MW: Request Intercepted
MW->>MW: Check Clerk Session Cookie
alt No Valid Session
MW-->>App: Redirect to /sign-in
App->>ClerkJS: Mount SignIn Component
User->>ClerkJS: Enter Credentials
ClerkJS->>ClerkAPI: Authenticate
ClerkAPI->>ClerkAPI: Verify Credentials
ClerkAPI-->>ClerkJS: JWT Token + Session
ClerkJS->>ClerkJS: Store Session Cookie
ClerkJS-->>App: Redirect to Original Route
else Valid Session Exists
MW->>MW: Verify JWT Signature
MW->>ClerkAPI: Validate Token (if needed)
ClerkAPI-->>MW: Token Valid
MW->>MW: Extract Organization ID
MW->>DB: Load Organization Data
DB-->>MW: Organization Data
MW-->>App: Allow Request
end
Note over User,DB: User Authenticated<br/>Organization Context Loaded

flowchart TB
Start([User Authenticated<br/>with Clerk])
GetClerkOrg[Get Active Organization<br/>from Clerk Session]
CheckOrg{Organization<br/>Exists?}
RedirectOnboard[Redirect to<br/>/onboarding/organization]
LoadOrgDB[Load Organization<br/>from Database]
CheckOrgDB{Org in<br/>Database?}
SyncOrg[Sync Organization<br/>from Clerk to DB]
LoadUser[Load OrganizationUser<br/>Record]
CheckUser{User<br/>Record Exists?}
CreateUser[Create OrganizationUser<br/>Record with Default Role]
LoadPermissions[Load User Permissions<br/>Role + Security Clearance]
StoreContext[Store in<br/>ServiceProviderContext]
Complete([User Ready<br/>with Full Context])
Start --> GetClerkOrg
GetClerkOrg --> CheckOrg
CheckOrg -->|No| RedirectOnboard
CheckOrg -->|Yes| LoadOrgDB
LoadOrgDB --> CheckOrgDB
CheckOrgDB -->|No| SyncOrg
CheckOrgDB -->|Yes| LoadUser
SyncOrg --> LoadUser
LoadUser --> CheckUser
CheckUser -->|No| CreateUser
CheckUser -->|Yes| LoadPermissions
CreateUser --> LoadPermissions
LoadPermissions --> StoreContext
StoreContext --> Complete
style RedirectOnboard fill:#fff3e0
style LoadPermissions fill:#e1f5fe
style Complete fill:#e8f5e9

graph TB
Request([API Request])
Layer1[Layer 1: Clerk JWT<br/>Signature Verification]
Pass1{Valid JWT?}
Fail1[401 Unauthorized]
Layer2[Layer 2: Organization<br/>Membership Check]
Pass2{Member of Org?}
Fail2[403 Forbidden]
Layer3[Layer 3: Role-Based<br/>Access Control]
Pass3{Has Required<br/>Role?}
Fail3[403 Forbidden]
Layer4[Layer 4: Security<br/>Clearance Check]
Pass4{Sufficient<br/>Clearance?}
Fail4[403 Insufficient Clearance]
Layer5[Layer 5: Resource-Level<br/>Permissions]
Pass5{Can Access<br/>Resource?}
Fail5[403 Forbidden]
Audit[Log Access<br/>POPIA Audit Trail]
Grant[Grant Access<br/>to Resource]
Request --> Layer1
Layer1 --> Pass1
Pass1 -->|No| Fail1
Pass1 -->|Yes| Layer2
Layer2 --> Pass2
Pass2 -->|No| Fail2
Pass2 -->|Yes| Layer3
Layer3 --> Pass3
Pass3 -->|No| Fail3
Pass3 -->|Yes| Layer4
Layer4 --> Pass4
Pass4 -->|No| Fail4
Pass4 -->|Yes| Layer5
Layer5 --> Pass5
Pass5 -->|No| Fail5
Pass5 -->|Yes| Audit
Audit --> Grant
style Fail1 fill:#ffebee
style Fail2 fill:#ffebee
style Fail3 fill:#ffebee
style Fail4 fill:#ffebee
style Fail5 fill:#ffebee
style Audit fill:#e1f5fe
style Grant fill:#e8f5e9

graph TB
subgraph Roles["User Roles"]
ADMIN[SERVICE_PROVIDER_ADMIN<br/>Full access to organization]
MANAGER[ACCOUNT_MANAGER<br/>Client & campaign management]
CREATOR[CONTENT_CREATOR<br/>Content creation & editing]
COMPLIANCE[COMPLIANCE_OFFICER<br/>Compliance & approval workflows]
LIAISON[GOVERNMENT_LIAISON<br/>Government client specialist]
ANALYST[ANALYST<br/>Read-only analytics access]
end
subgraph Permissions["Permission Matrix"]
direction TB
P1[Organization Management]
P2[Client Management]
P3[Campaign Management]
P4[Content Creation]
P5[Content Approval]
P6[Analytics Access]
P7[Team Management]
P8[Billing Management]
P9[Compliance Configuration]
P10[API Key Management]
end
ADMIN -.Full Access.-> P1
ADMIN -.Full Access.-> P2
ADMIN -.Full Access.-> P3
ADMIN -.Full Access.-> P4
ADMIN -.Full Access.-> P5
ADMIN -.Full Access.-> P6
ADMIN -.Full Access.-> P7
ADMIN -.Full Access.-> P8
ADMIN -.Full Access.-> P9
ADMIN -.Full Access.-> P10
MANAGER --> P2
MANAGER --> P3
MANAGER --> P6
CREATOR --> P4
CREATOR -.Read Only.-> P3
COMPLIANCE --> P5
COMPLIANCE --> P9
COMPLIANCE -.Read Only.-> P6
LIAISON --> P2
LIAISON --> P3
LIAISON --> P4
LIAISON -.Government Only.-> P5
ANALYST -.Read Only.-> P6
style ADMIN fill:#e3f2fd
style COMPLIANCE fill:#f3e5f5
style LIAISON fill:#fff3e0

Role Capabilities:

RoleOrganizationClientsCampaignsContentApprovalAnalyticsTeamBilling
SERVICE_PROVIDER_ADMINFullFullFullFullFullFullFullFull
ACCOUNT_MANAGERReadFullFullCreate-Read--
CONTENT_CREATORReadReadReadFull----
COMPLIANCE_OFFICERReadReadReadReadFullRead--
GOVERNMENT_LIAISONReadGov OnlyGov OnlyGov OnlyGov OnlyGov Only--
ANALYSTReadReadRead--Full--

graph TB
subgraph Clearance["Security Clearance Levels"]
BASIC[BASIC Clearance<br/>Municipal/Local Government<br/>PUBLIC data only]
ENHANCED[ENHANCED Clearance<br/>Provincial Government<br/>CONFIDENTIAL data allowed]
CONFIDENTIAL[CONFIDENTIAL Clearance<br/>National Government<br/>RESTRICTED data allowed]
end
subgraph DataClass["Data Classification"]
PUBLIC[PUBLIC<br/>Publicly available data]
COMMERCIAL[COMMERCIAL<br/>Commercial sensitive data]
CONF[CONFIDENTIAL<br/>Government confidential]
RESTRICTED[RESTRICTED<br/>National security data]
end
subgraph Access["Access Control"]
Check[Security Clearance<br/>Validation]
Allow[Access Granted]
Deny[Access Denied]
Audit[Audit Log Entry]
end
BASIC -.Can Access.-> PUBLIC
BASIC -.Can Access.-> COMMERCIAL
ENHANCED -.Can Access.-> PUBLIC
ENHANCED -.Can Access.-> COMMERCIAL
ENHANCED -.Can Access.-> CONF
CONFIDENTIAL -.Can Access.-> PUBLIC
CONFIDENTIAL -.Can Access.-> COMMERCIAL
CONFIDENTIAL -.Can Access.-> CONF
CONFIDENTIAL -.Can Access.-> RESTRICTED
PUBLIC --> Check
COMMERCIAL --> Check
CONF --> Check
RESTRICTED --> Check
Check --> Allow
Check --> Deny
Allow --> Audit
Deny --> Audit
style BASIC fill:#e8f5e9
style ENHANCED fill:#fff3e0
style CONFIDENTIAL fill:#ffebee
style RESTRICTED fill:#ffcdd2
style Audit fill:#e1f5fe

Clearance Requirements:

Client TypeRequired ClearanceData Classification
Municipal GovernmentBASICPUBLIC
Provincial GovernmentENHANCEDPUBLIC, CONFIDENTIAL
National GovernmentCONFIDENTIALPUBLIC, CONFIDENTIAL, RESTRICTED
Business (SME)BASICPUBLIC, COMMERCIAL
Business (Large Corp)BASICPUBLIC, COMMERCIAL

flowchart TB
Start([HTTP Request])
MW[Next.js Middleware<br/>middleware.ts]
CheckPath{Protected<br/>Path?}
AllowPublic[Allow Request<br/>No Auth Required]
ClerkMW[Clerk Middleware<br/>clerkMiddleware]
ValidateJWT[Validate JWT Token]
ValidJWT{Valid Token?}
Redirect401[Redirect to /sign-in]
ExtractClaims[Extract JWT Claims<br/>- User ID<br/>- Organization ID<br/>- Session ID]
CheckOrg{Has Active<br/>Organization?}
RedirectOnboard[Redirect to<br/>/onboarding/organization]
AddHeaders[Add Auth Headers<br/>- X-User-Id<br/>- X-Organization-Id<br/>- X-Session-Id]
Continue[Continue to<br/>Route Handler]
Start --> MW
MW --> CheckPath
CheckPath -->|Public| AllowPublic
CheckPath -->|Protected| ClerkMW
ClerkMW --> ValidateJWT
ValidateJWT --> ValidJWT
ValidJWT -->|No| Redirect401
ValidJWT -->|Yes| ExtractClaims
ExtractClaims --> CheckOrg
CheckOrg -->|No| RedirectOnboard
CheckOrg -->|Yes| AddHeaders
AddHeaders --> Continue
style Redirect401 fill:#ffebee
style RedirectOnboard fill:#fff3e0
style Continue fill:#e8f5e9

Middleware Code Pattern:

middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
const isProtectedRoute = createRouteMatcher([
'/dashboard(.*)',
'/api/(?!webhooks)(.*)',
])
export default clerkMiddleware((auth, req) => {
if (isProtectedRoute(req)) {
auth().protect()
const { userId, orgId } = auth()
if (!orgId) {
return NextResponse.redirect(new URL('/onboarding/organization', req.url))
}
// Add auth context to headers
const requestHeaders = new Headers(req.headers)
requestHeaders.set('x-user-id', userId)
requestHeaders.set('x-organization-id', orgId)
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
}
})

stateDiagram-v2
[*] --> Unauthenticated
Unauthenticated --> SignIn: User Signs In
SignIn --> Authenticated: Clerk Creates Session
Authenticated --> Active: User Activity
Active --> Authenticated: Refresh Token
Authenticated --> Idle: No Activity
Idle --> Authenticated: User Returns
Idle --> Expired: Timeout (30min default)
Authenticated --> SignOut: User Signs Out
SignOut --> Unauthenticated: Clerk Destroys Session
Expired --> Unauthenticated: Session Expired
Unauthenticated --> [*]
note right of Authenticated
Clerk JWT Token:
- Valid for 60 seconds
- Auto-refreshed
- Contains user + org claims
end note
note right of Idle
Configurable Timeout:
- Default: 30 minutes
- Government: 15 minutes
- Can be customized per org
end note

Session Configuration:

// Security Configuration Model
interface SecurityConfiguration {
sessionTimeoutMinutes: number // Default: 30, Government: 15
mfaRequired: boolean // Required for CONFIDENTIAL clearance
ipWhitelistEnabled: boolean // Optional per organization
sessionRefreshEnabled: boolean // Auto-refresh JWT
}

sequenceDiagram
participant Client as API Client
participant API as API Gateway
participant KeyValidator as API Key Validator
participant DB as Database
participant RateLimit as Rate Limiter
participant Handler as API Handler
participant Audit as Audit Logger
Client->>API: Request with API Key<br/>Header: X-API-Key
API->>KeyValidator: Validate Key
KeyValidator->>DB: Query ApiKey Table
DB-->>KeyValidator: Key Data
alt Key Invalid
KeyValidator-->>API: Invalid Key
API-->>Client: 401 Unauthorized
else Key Valid
KeyValidator->>KeyValidator: Check Expiry
KeyValidator->>KeyValidator: Verify Permissions
KeyValidator-->>API: Key Valid
API->>RateLimit: Check Rate Limit
alt Rate Limit Exceeded
RateLimit-->>API: Limit Exceeded
API-->>Client: 429 Too Many Requests
else Within Limit
RateLimit-->>API: Allowed
API->>Handler: Forward Request
Handler->>Handler: Process Request
Handler-->>API: Response
API->>Audit: Log API Usage
Audit->>DB: Store Audit Log
API-->>Client: 200 Success
end
end
Note over Client,Audit: API Key Protected<br/>Rate Limited & Audited

API Key Model:

interface ApiKey {
id: string
organizationId: string
key: string // Encrypted
name: string
permissions: {
endpoints: string[] // Allowed endpoints
methods: string[] // Allowed HTTP methods
rateLimit: number // Requests per minute
}
expiresAt?: Date
lastUsedAt?: Date
createdAt: Date
}

flowchart TB
Start([Webhook Event])
ExtractSig[Extract Signature<br/>from Headers]
LoadSecret[Load Webhook Secret<br/>from Database]
ComputeHMAC[Compute HMAC-SHA256<br/>of Request Body]
CompareSig{Signatures<br/>Match?}
CheckTimestamp[Check Timestamp<br/>< 5 minutes old]
ValidTime{Valid<br/>Timestamp?}
CheckIP[Check IP Whitelist<br/>if Enabled]
ValidIP{IP<br/>Allowed?}
RejectReplay[Reject: Replay Attack]
RejectUnauth[Reject: Unauthorized]
RejectIP[Reject: IP Not Whitelisted]
LogWebhook[Log Webhook Delivery<br/>to Database]
ProcessEvent[Process Webhook Event]
AuditLog[Create Audit Log<br/>POPIA Compliance]
Success([200 OK])
Start --> ExtractSig
ExtractSig --> LoadSecret
LoadSecret --> ComputeHMAC
ComputeHMAC --> CompareSig
CompareSig -->|No| RejectUnauth
CompareSig -->|Yes| CheckTimestamp
CheckTimestamp --> ValidTime
ValidTime -->|No| RejectReplay
ValidTime -->|Yes| CheckIP
CheckIP --> ValidIP
ValidIP -->|No| RejectIP
ValidIP -->|Yes| LogWebhook
LogWebhook --> ProcessEvent
ProcessEvent --> AuditLog
AuditLog --> Success
style RejectReplay fill:#ffebee
style RejectUnauth fill:#ffebee
style RejectIP fill:#ffebee
style Success fill:#e8f5e9
style AuditLog fill:#e1f5fe

Webhook Security Implementation:

// Webhook signature verification
async function verifyWebhookSignature(
payload: string,
signature: string,
secret: string,
timestamp: string
): Promise<boolean> {
// Check timestamp (prevent replay attacks)
const eventTime = new Date(timestamp)
const now = new Date()
const diff = Math.abs(now.getTime() - eventTime.getTime())
if (diff > 5 * 60 * 1000) { // 5 minutes
return false
}
// Compute HMAC
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${payload}`)
.digest('hex')
// Constant-time comparison
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}

flowchart TB
AuthEvent([Authentication Event])
Classify{Event Type?}
SignIn[Sign In Event]
SignOut[Sign Out Event]
Failed[Failed Auth]
OrgSwitch[Organization Switch]
PermChange[Permission Change]
GatherData[Gather Event Data<br/>- User ID<br/>- IP Address<br/>- User Agent<br/>- Organization<br/>- Timestamp<br/>- Result]
SanitizePII[Sanitize PII<br/>Remove sensitive data<br/>from logs]
CreateAudit[Create AuditLog<br/>Record]
StoreDB[(Store in<br/>PostgreSQL)]
CheckAlert{Critical<br/>Event?}
SendAlert[Send Alert to<br/>Compliance Officer]
Complete([Audit Complete])
AuthEvent --> Classify
Classify --> SignIn
Classify --> SignOut
Classify --> Failed
Classify --> OrgSwitch
Classify --> PermChange
SignIn --> GatherData
SignOut --> GatherData
Failed --> GatherData
OrgSwitch --> GatherData
PermChange --> GatherData
GatherData --> SanitizePII
SanitizePII --> CreateAudit
CreateAudit --> StoreDB
StoreDB --> CheckAlert
CheckAlert -->|Yes| SendAlert
CheckAlert -->|No| Complete
SendAlert --> Complete
style Failed fill:#ffebee
style SanitizePII fill:#fff3e0
style StoreDB fill:#e1f5fe
style Complete fill:#e8f5e9

Audited Authentication Events:

EventDescriptionPOPIA Requirement
USER_SIGN_INUser successfully authenticatedYes - Access logging
USER_SIGN_OUTUser logged outYes - Session termination
AUTH_FAILEDFailed authentication attemptYes - Security monitoring
ORG_SWITCHUser switched organizationsYes - Context change
PERMISSION_GRANTEDNew permissions assignedYes - Access change
PERMISSION_REVOKEDPermissions removedYes - Access change
SESSION_EXPIREDSession timed outYes - Session lifecycle
MFA_ENABLEDMFA activatedYes - Security enhancement
PASSWORD_CHANGEDPassword updatedYes - Security event
API_KEY_CREATEDNew API key generatedYes - Access credential

  1. Strong Passwords: Enforce minimum requirements via Clerk settings
  2. MFA Required: Mandatory for CONFIDENTIAL security clearance
  3. Session Timeout: 15 minutes for government, 30 minutes for business
  4. JWT Rotation: Automatic token refresh every 60 seconds
  5. IP Whitelisting: Optional per organization for sensitive operations
  1. Principle of Least Privilege: Users get minimum required permissions
  2. Defense in Depth: Multiple authorization layers
  3. Security Clearance: Mandatory for government client access
  4. Resource-Level Checks: Verify access to specific resources
  5. Audit Everything: Complete audit trail for compliance
  1. Consent Tracking: Record consent for authentication
  2. Data Minimization: Store only necessary auth data
  3. Right to Access: Users can view their auth history
  4. Right to Deletion: Support for account deletion
  5. Data Residency: All auth data in South African servers

.env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
# Optional: Custom domains
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding/organization
// SecurityConfiguration defaults
const DEFAULT_SECURITY = {
sessionTimeoutMinutes: 30,
mfaRequired: false,
ipWhitelistEnabled: false,
allowedOrigins: ['https://thrivesend.isutech.co.za'],
corsEnabled: true,
rateLimitPerMinute: 60,
}
// Government overrides
const GOVERNMENT_SECURITY = {
...DEFAULT_SECURITY,
sessionTimeoutMinutes: 15,
mfaRequired: true, // For CONFIDENTIAL clearance
ipWhitelistEnabled: true,
}


Last Updated: November 5, 2025 Verified: Production v0.1.4