How Pentagi Handles User Authentication and Authorization: A Deep Dive into the Go Implementation
Pentagi implements a multi-layered security model using session-based login, OAuth2 SSO (Google/GitHub), and JWT API tokens, enforced through Gin middleware that validates privileges per route.
Pentagi, an open-source AI security automation platform developed by vxcontrol, secures its REST API and GraphQL endpoints through a comprehensive authentication and authorization pipeline. The system supports multiple identity providers and enforces fine-grained access control using privilege-based middleware in the Gin web framework.
Session-Based Local Authentication
Login Flow and Cookie Creation
Local users authenticate via POST /api/v1/auth/login, handled by the AuthLogin function in backend/pkg/server/services/auth.go. The process validates the bcrypt password hash against the users table, verifies the account is active, and retrieves the user's privileges from the privileges table.
Upon successful validation, Pentagi creates a signed Gin session using github.com/gin-contrib/sessions:
session.Set("uid", user.ID)
session.Set("uhash", user.Hash)
session.Set("rid", user.RoleID)
session.Set("tid", models.UserTypeLocal.String())
session.Set("prm", privs) // list of strings
session.Set("gtm", time.Now().Unix())
session.Set("exp", time.Now().Add(timeout).Unix())
The session is persisted to an HTTP-only cookie using auth.MakeCookieStoreKey.
Session Validation Middleware
Every request passes through AuthMiddleware.tryAuth, which invokes tryUserCookieAuthentication in backend/pkg/server/auth/auth_middleware.go. This middleware:
- Reads the session cookie and extracts claims (
uid,uhash,exp, etc.) - Verifies the session has not expired
- Validates the user hash matches the current database value (preventing cookie reuse after password changes)
- Stores the identity in the Gin context (
c.Set("uid", ...)) for downstream handlers
Sessions that fail any check return authResultFail, forcing re-authentication.
OAuth2 Single Sign-On (Google and GitHub)
Authorization Request Flow
Pentagi supports OAuth2 SSO through GET /api/v1/auth/authorize, handled by AuthAuthorize in backend/pkg/server/services/auth.go. The endpoint:
- Generates a cryptographically random state parameter (signed HMAC with JSON payload)
- Stores a nonce in a SameSite-appropriate cookie
- Redirects the client to the provider's authorization URL (
oauthClient.AuthCodeURL)
Callback Handling and User Provisioning
After provider authentication, the callback handlers (authLoginCallback and related functions) process the authorization code:
- Exchange the code for an access token using the OAuth2 client
- Resolve the user's email via
oauthClient.ResolveEmail - If the email does not exist in the
userstable, create a new OAuth user record - Build a session cookie identical to the local login flow, setting
tidtomodels.UserTypeOAuth
This ensures SSO users receive the same session-based privileges as local users.
API Token Authentication with JWT
Token Structure and Validation
Pentagi implements machine-to-machine authentication using JWT API tokens defined in backend/pkg/server/auth/api_token_jwt.go. The token structure uses the APITokenClaims struct:
type APITokenClaims struct {
TokenID string `json:"tid"`
UID uint64 `json:"uid"`
RID uint64 `json:"rid"`
UHASH string `json:"uhash"`
ExpiresAt time.Time `json:"exp"`
}
The ValidateAPIToken function parses the JWT, verifies the HMAC signature using the global cfg.CookieSigningSalt, and returns the claims for further processing.
Permission Lookup and Caching
API tokens receive their privileges through TokenCache in backend/pkg/server/auth/api_token_cache.go. The GetStatus method:
- Loads the token row from the database
- Fetches the associated role's privileges
- Adds the built-in
pentagi.automationprivilege (required for all API tokens) - Caches the result for 5 minutes to reduce database load
This ensures API tokens have both role-based and automation-specific permissions.
Request-Level Authorization and Middleware
Authentication Middleware (AuthUserRequired vs AuthTokenRequired)
Pentagi uses three distinct middleware patterns in backend/pkg/server/auth/auth_middleware.go:
TryAuth: Attempts authentication but allows anonymous access (used for public endpoints)AuthUserRequired: Enforces valid session cookies; rejects API tokensAuthTokenRequired: Enforces valid API tokens (JWT); rejects session cookies
The AuthTokenRequired middleware specifically invokes tryProtoTokenAuthentication, which extracts the Bearer token from the Authorization header, validates it via ValidateAPIToken, and populates the Gin context with uid, rid, prm, and other identity fields.
Privilege-Based Access Control
Route handlers enforce fine-grained permissions using auth.PrivilegesRequired from backend/pkg/server/auth/permissions.go. This middleware:
- Reads
c.GetStringSlice("prm")(the privilege list stored by authentication middleware) - Compares against the required permissions passed as arguments
- Aborts with HTTP 403 if any required privilege is missing
Example implementation:
router.Group("/api/v1").
Use(authMiddleware.AuthUserRequired).
GET("/admin/users", auth.PrivilegesRequired("users.read"), userService.GetUsers)
Only sessions or API tokens containing the users.read privilege can access this endpoint.
Performance Optimizations with Caching
Pentagi implements two specialized caches to minimize database queries during authentication:
-
UserCache (
backend/pkg/server/auth/users_cache.go): Stores<userID> → (hash, status)mappings for 5 minutes. This prevents repeated database lookups when validating session cookies on every request. -
TokenCache (
backend/pkg/server/auth/api_token_cache.go): Stores<tokenID> → (status, privileges)mappings with the same 5-minute expiration. This accelerates API token validation by avoiding privilege lookups on everyauthenticated request.
Both caches invalidate entries immediately when users or tokens are updated (e.g., password changes or token revocation), ensuring security is not compromised by stale data.
Summary
- Pentagi uses a layered security model supporting local sessions, OAuth2 SSO (Google/GitHub), and JWT API tokens.
- Session authentication relies on signed Gin cookies with bcrypt password validation and hash-based replay protection.
- OAuth2 flow generates signed state parameters, handles provider callbacks, and provisions new users automatically.
- API tokens use HMAC-signed JWTs with built-in
pentagi.automationprivileges and cached permission lookups. - Middleware enforces authentication via
AuthUserRequired(sessions) orAuthTokenRequired(JWTs), whilePrivilegesRequiredhandles fine-grained authorization. - Caching layers (
UserCache,TokenCache) optimize performance by reducing database queries during request validation.
Frequently Asked Questions
What authentication methods does Pentagi support?
Pentagi supports three primary authentication methods: local session-based login using email and password with bcrypt hashing, OAuth2 Single Sign-On through Google and GitHub providers, and API token authentication using JWTs for machine-to-machine access. All methods ultimately populate the Gin context with user identity and privilege information.
How does Pentagi validate API tokens?
API tokens are validated in backend/pkg/server/auth/api_token_jwt.go through the ValidateAPIToken function. The system parses the JWT, verifies the HMAC signature using the global CookieSigningSalt, and extracts claims including TokenID, UID, RID, and ExpiresAt. The TokenCache then loads the corresponding privileges and adds the mandatory pentagi.automation permission.
What is the difference between AuthUserRequired and AuthTokenRequired?
AuthUserRequired enforces session cookie authentication, rejecting requests that present API tokens and requiring valid session data from the Gin session store. AuthTokenRequired enforces JWT bearer token authentication, extracting tokens from the Authorization header and rejecting session-based requests. Both middleware populate the same context keys (uid, prm, etc.), allowing downstream handlers to work with either authentication type.
How are user privileges checked in Pentagi?
Privilege checks occur through the PrivilegesRequired middleware in backend/pkg/server/auth/permissions.go. This middleware reads the prm (privileges) slice from the Gin context, which was populated by either session or token authentication middleware, and verifies that the user possesses all required permissions. If any privilege is missing, the middleware aborts the request with HTTP 403 before the route handler executes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →