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

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:

  1. Reads the session cookie and extracts claims (uid, uhash, exp, etc.)
  2. Verifies the session has not expired
  3. Validates the user hash matches the current database value (preventing cookie reuse after password changes)
  4. 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:

  1. Generates a cryptographically random state parameter (signed HMAC with JSON payload)
  2. Stores a nonce in a SameSite-appropriate cookie
  3. 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:

  1. Exchange the code for an access token using the OAuth2 client
  2. Resolve the user's email via oauthClient.ResolveEmail
  3. If the email does not exist in the users table, create a new OAuth user record
  4. Build a session cookie identical to the local login flow, setting tid to models.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:

  1. Loads the token row from the database
  2. Fetches the associated role's privileges
  3. Adds the built-in pentagi.automation privilege (required for all API tokens)
  4. 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 tokens
  • AuthTokenRequired: 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:

  1. Reads c.GetStringSlice("prm") (the privilege list stored by authentication middleware)
  2. Compares against the required permissions passed as arguments
  3. 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.automation privileges and cached permission lookups.
  • Middleware enforces authentication via AuthUserRequired (sessions) or AuthTokenRequired (JWTs), while PrivilegesRequired handles 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →