How Gitea Handles User Authentication Flows: A Deep Dive into the go-gitea/gitea Source Code
Gitea processes every authentication request through a three-phase pipeline—request entry, credential validation against pluggable auth sources, and session establishment with optional two-factor enforcement—implemented across routers/web/auth/auth.go, services/auth/signin.go, and services/auth/basic.go.
Gitea's authentication system provides a flexible, pluggable architecture that supports everything from local database credentials to LDAP and OAuth2 providers. Understanding how Gitea handles user authentication flows requires examining the interplay between HTTP routers, authentication methods, and session management in the go-gitea/gitea codebase.
The Three-Phase Authentication Architecture
Every incoming request passes through a standardized authentication pipeline divided into three distinct phases. This architecture ensures consistent security enforcement regardless of whether the user is accessing the web UI, API endpoints, or static resources.
| Phase | Description | Key Source Files |
|---|---|---|
| Request Entry | HTTP router directs traffic to authentication-aware handlers | routers/web/auth/auth.go – SignIn, autoSignIn |
| Credential Validation | Auth methods extract and verify credentials (cookies, headers, tokens) | services/auth/basic.go, services/auth/signin.go, services/auth/oauth2 |
| Session & Post-Login | Session creation, 2FA/WebAuthn enforcement, and redirection | routers/web/auth/auth.go – handleSignInFull, updateSession |
Phase 1: Request Entry and Auto-Login
When a request first hits the Gitea server, the routing layer determines whether the user already has valid credentials or must authenticate.
Automatic "Remember Me" Validation
Before presenting a login page, Gitea attempts silent authentication via the autoSignIn function. This mechanism checks for the presence of a remember_token cookie established during previous sessions.
t, err := auth_service.CheckAuthToken(ctx, ctx.GetSiteCookie(setting.CookieRememberName))
If the token validates successfully, autoSignIn generates a fresh token, populates the session with the user's identity (uid, uname, userHasTwoFactorAuth), and treats the request as authenticated. This logic resides in routers/web/auth/auth.go and allows returning users to bypass the login screen entirely while maintaining security through rotating tokens.
Phase 2: Credential Validation
When automatic login fails or the user explicitly submits credentials, Gitea enters the validation phase. The system supports multiple authentication methods that extract credentials from different transport mechanisms.
Standard Username/Password Authentication
Web UI login requests POST to /user/login, which invokes SignInPost in routers/web/auth/auth.go. This handler delegates credential verification to UserSignIn in services/auth/signin.go:
u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password)
The UserSignIn function implements intelligent identifier resolution:
- Email detection: If the supplied identifier contains "@", Gitea queries the
email_addresstable - Username normalization: Otherwise, the identifier is converted to lowercase for the username lookup
- Source resolution: The retrieved
user_model.Usercontains aLoginSourcefield pointing to anauth.Source(LDAP, SMTP, PAM, or local database) - Password verification: The source's
PasswordAuthenticatorimplementation validates the credential against the respective backend
If the authentication source is inactive, the function returns oauth2.ErrAuthSourceNotActivated, preventing login through disabled providers.
HTTP Basic Auth and API Tokens
API requests utilize Basic.Verify in services/auth/basic.go to extract the Authorization header. This method implements a cascading validation strategy:
uid := GetOAuthAccessTokenScopeAndUserID(req.Context(), authToken)
token, err := auth_model.GetAccessTokenBySHA(req.Context(), authToken)
First, the verifier attempts to interpret the header value as an OAuth2 token or personal access token. If these checks fail and Basic Auth is enabled, the system falls back to standard password verification via UserSignIn.
Notably, Basic.Verify enforces security policies by refusing Basic Auth when the user has enrolled WebAuthn devices, returning ErrUserAuthMessage to prevent credential stuffing against accounts with hardware-based two-factor authentication.
OAuth2 and External Providers
For OAuth2 flows (GitHub, GitLab, OpenID Connect), the system bypasses UserSignIn entirely. After the provider redirects back to the callback endpoint in routers/web/auth/oauth.go, the OAuth2 source creates or links a Gitea user directly:
func OAuth2Callback(ctx *context.Context) {
// Exchange code for token, fetch user info
u := oauth2Source.LoginUser(gothUser) // creates/links Gitea user
handleSignIn(ctx, u, false)
}
Phase 3: Session Management and Two-Factor Enforcement
Once credentials validate successfully, Gitea establishes a session and enforces additional security controls before granting access.
Two-Factor Authentication and WebAuthn Checks
After UserSignIn returns a valid user object, SignInPost inspects the user's security configuration:
hasTOTPtwofa, err := auth.HasTwoFactorByUID(ctx, u.ID)
hasWebAuthnTwofa, err := auth.HasWebAuthnRegistrationsByUID(ctx, u.ID)
The authentication flow branches based on these checks:
- No 2FA: Immediate login via
handleSignIn - TOTP enabled: Redirect to
/user/two_factorfor time-based code verification - WebAuthn enrolled: Redirect to
/user/webauthnfor hardware key authentication
This enforcement ensures that even with valid primary credentials, users must complete secondary verification before accessing protected resources.
Session Creation and Locale Handling
The handleSignInFull function in routers/web/auth/auth.go orchestrates session establishment:
if err := updateSession(ctx, nil, map[string]any{
session.KeyUID: u.ID,
session.KeyUname: u.Name,
session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth,
}); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
This routine performs several critical tasks:
- Session regeneration: Creates a new session to prevent session fixation attacks
- Remember me handling: Optionally writes a long-lived authentication token if the user selected "remember me"
- Locale synchronization: Updates the user's language preference if unset
- Audit logging: Registers the last-login timestamp for security monitoring
Pluggable Authentication Sources
Gitea's architecture supports enterprise authentication through a modular source system. All external backends implement the PasswordAuthenticator interface defined in services/auth/interface.go.
The built-in sources auto-register via blank imports in services/auth/signin.go:
_ "code.gitea.io/gitea/services/auth/source/db"
_ "code.gitea.io/gitea/services/auth/source/ldap"
_ "code.gitea.io/gitea/services/auth/source/pam"
_ "code.gitea.io/gitea/services/auth/source/sspi"
_ "code.gitea.io/gitea/services/auth/source/smtp"
Each source resides under services/auth/source/<type>/ and provides both authentication and user synchronization capabilities. For example, LDAP sources implement source_authenticate.go to bind against directory servers, while SMTP sources verify credentials against mail servers.
Code Examples in Action
Web UI Login Flow
The standard web authentication flow begins with form submission to the SignInPost handler:
// POST /user/login → SignInPost
func SignInPost(ctx *context.Context) {
// Form validation omitted for brevity
u, source, err := auth_service.UserSignIn(ctx,
ctx.FormString("user_name"),
ctx.FormString("password"))
// u contains the authenticated user
// source indicates the authentication backend (local, LDAP, etc.)
// 2FA redirect logic follows...
}
API Access with Personal Tokens
For programmatic access, clients can authenticate using personal access tokens:
curl -H "Authorization: token 0123456789abcdef" \
https://gitea.example.com/api/v1/user
Basic.VerifyAuthToken processes this header by looking up the SHA hash via auth_model.GetAccessTokenBySHA, setting LoginMethod = "access_token" in the request store, and returning the associated user object.
Command-Line Basic Authentication
For scripts that require password authentication:
curl -u myuser:mypassword "https://gitea.example.com/api/v1/user"
The Basic.Verify handler extracts these credentials, attempts token interpretation first, then falls back to UserSignIn for password validation against the configured authentication sources.
Summary
- Three-phase architecture: Gitea authentication moves sequentially through request entry (cookie/token detection), credential validation (source-specific verification), and session establishment (2FA enforcement and cookie generation)
- Pluggable sources: The
PasswordAuthenticatorinterface allows seamless integration of LDAP, SMTP, PAM, SSPI, and local database backends through auto-registered packages inservices/auth/source/ - Security layering: WebAuthn enrollment blocks Basic Auth attempts, while
handleSignInFullregenerates sessions and handles "remember me" tokens securely - Dual-path validation:
services/auth/signin.gohandles web UI passwords with email/username flexibility, whileservices/auth/basic.gomanages API authentication with OAuth2, personal tokens, and fallback Basic Auth
Frequently Asked Questions
How does Gitea's "remember me" feature work?
The "remember me" functionality uses rotating authentication tokens stored in the remember_token cookie. When a request arrives without an active session, autoSignIn in routers/web/auth/auth.go validates the token against the database via CheckAuthToken. Upon successful validation, Gitea generates a fresh token, updates the cookie, and populates the session with the user's identity, allowing persistent authentication across browser sessions without storing passwords client-side.
Why does Basic Auth fail for users with WebAuthn enabled?
As implemented in services/auth/basic.go, the Verify method explicitly checks for WebAuthn enrollments and returns ErrUserAuthMessage when hardware-based two-factor authentication is detected. This security measure prevents automated brute-force attacks against high-security accounts using password-only authentication. Users with WebAuthn must use personal access tokens or OAuth2 for API access instead of password-based Basic Auth.
How does Gitea distinguish between email addresses and usernames during login?
In services/auth/signin.go, the UserSignIn function performs string inspection on the provided identifier. If the input contains the "@" character, Gitea queries the email_address table to resolve the corresponding user ID. Otherwise, the identifier is normalized to lowercase and matched against the user table's username column. This dual-lookup approach allows users to authenticate using either their registered email or username without requiring explicit selection.
Can Gitea authenticate API requests using OAuth2 tokens?
Yes. The Basic.Verify method in services/auth/basic.go first attempts to validate the Authorization header as an OAuth2 access token through GetOAuthAccessTokenScopeAndUserID. This occurs before checking for personal access tokens or falling back to password authentication. The OAuth2 validation extracts the user ID and scope from the token, enabling fine-grained permissions for third-party applications accessing the Gitea API.
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 →