OAuth Authentication Flow in OpenDeepWiki: A Complete Technical Guide
OpenDeepWiki implements a standard OAuth 2.0 Authorization Code flow that exchanges provider codes for JWT tokens through three distinct architectural layers.
The OAuth authentication flow in OpenDeepWiki enables secure sign-in via external providers like GitHub and Gitee without storing user passwords. This article examines the complete implementation across endpoint, service, and data layers based on the actual source code in the AIDotNet/OpenDeepWiki repository.
OAuth 2.0 Authorization Code Flow Overview
OpenDeepWiki follows the classic Authorization Code grant type defined in RFC 6749. The flow separates user authentication from application authorization by introducing a temporary authorization code that the backend exchanges for an access token. This architecture keeps sensitive tokens server-side while returning a signed JWT (JSON Web Token) to the client for subsequent authenticated requests.
Endpoint Layer: HTTP Routes for OAuth
The public API surface is defined in [OAuthEndpoints.cs](https://github.com/AIDotNet/OpenDeepWiki/blob/main/src/OpenDeepWiki/Endpoints/OAuthEndpoints.cs), which maps minimal API routes to service methods.
Authorization Endpoint
The GET /api/oauth/{provider}/authorize route constructs the provider-specific consent URL. It loads the OAuthProvider configuration from the database and returns a JSON payload containing the authorizationUrl that the frontend uses to redirect the browser.
Callback Endpoint
The GET /api/oauth/{provider}/callback route receives the code and state parameters from the provider after user consent. This endpoint delegates to OAuthService.HandleCallbackAsync, which orchestrates token exchange, user lookup, and JWT generation. The response returns a LoginResponse containing the AccessToken, expiration, and user profile.
Convenience Shortcuts
For direct browser redirects, the endpoint layer exposes shortcut routes such as /api/oauth/github/login and /api/oauth/gitee/login. These immediately redirect to the provider's consent page without requiring a two-step API call from the client.
Service Layer: OAuthService Implementation
The core logic resides in [OAuthService.cs](https://github.com/AIDotNet/OpenDeepWiki/blob/main/src/OpenDeepWiki/Services/OAuth/OAuthService.cs), implementing the IOAuthService contract.
Building the Authorization URL
The GetAuthorizationUrlAsync method queries the database for the active OAuthProvider record. It constructs the authorization query string with the following required parameters:
client_idredirect_uriresponse_type=codestate(CSRF protection token)scope(optional, provider-specific permissions)
Handling the Callback and Token Exchange
The HandleCallbackAsync method executes the critical post-redirect sequence:
-
ExchangeCodeForTokenAsync – Posts the authorization code to the provider's token endpoint using
client_secretauthentication. Parses the JSON response into anOAuthTokenResponsecontainingaccess_tokenand optionalrefresh_token. -
GetOAuthUserInfoAsync – Calls the provider's user-info endpoint with the bearer token. Maps the provider-specific JSON structure to a uniform
OAuthUserInfoobject using either customUserInfoMappingconfiguration or built-in defaults for GitHub and Gitee. -
FindOrCreateUserAsync – Implements the identity linking strategy:
- Searches for an existing
UserOAuthbinding by provider and external ID. - If no binding exists, attempts to match by email address.
- If no user exists, creates a new
Userrecord with the default "User" role. - Persists a new
UserOAuthrecord linking the local user to the external identity.
- Searches for an existing
-
GetUserRolesAsync – Retrieves all role names assigned to the user.
-
IJwtService.GenerateToken – Creates a signed JWT containing claims for user ID, name, email, avatar URL, and roles.
The final LoginResponse includes the AccessToken, expiration timestamp, and a UserInfo payload ready for client-side storage.
Data Layer: Provider Configuration and User Bindings
Persistent storage is defined in two key entities:
OAuthProvider ([OAuthProvider.cs](https://github.com/AIDotNet/OpenDeepWiki/blob/main/src/OpenDeepWiki.Entities/OAuth/OAuthProvider.cs)) stores provider-specific configuration:
ClientIdandClientSecretAuthorizationUrl,TokenUrl, andUserInfoUrl- Optional
Scopestring - Optional
UserInfoMappingJSON configuration for custom field mapping
UserOAuth ([UserOAuth.cs](https://github.com/AIDotNet/OpenDeepWiki/blob/main/src/OpenDeepWiki.Entities/OAuth/UserOAuth.cs)) records the binding between local users and external identities:
- Foreign keys to
UserandOAuthProvider - External provider user ID
- Access tokens, refresh tokens, and expiry
- Avatar URL and metadata timestamps
Code Examples
Server-Side: Initiating the Flow
// In a controller or minimal API endpoint
var authUrl = await _oauthService.GetAuthorizationUrlAsync("github");
// Redirect the client to the provider's consent page
return Results.Redirect(authUrl);
Server-Side: Handling the Callback
// provider = "github", code = request.Query["code"]
var loginResult = await _oauthService.HandleCallbackAsync(provider, code);
// loginResult.AccessToken contains the JWT for the frontend
return Results.Ok(loginResult);
Client-Side: Next.js Integration
// Step 1 – Obtain the authorization URL
const resp = await fetch('/api/oauth/github/authorize');
const { authorizationUrl } = (await resp.json()).data;
// Step 2 – Redirect the browser to the provider
window.location.href = authorizationUrl;
// Step 3 – After the provider redirects back to /api/oauth/github/callback
// The endpoint returns { accessToken, expiresIn, user } which you store
// in memory or secure HTTP-only cookies
Summary
- OpenDeepWiki implements a standard OAuth 2.0 Authorization Code flow with clear separation between HTTP endpoints, business logic, and data persistence.
- The
OAuthServiceclass orchestrates the complete lifecycle: URL generation, token exchange, user mapping, and JWT issuance. - Provider configuration is dynamic and database-driven via the
OAuthProviderentity, supporting custom endpoints and field mappings without code changes. - User identity linking follows a fallback strategy: external ID binding first, then email matching, then new user creation with default roles.
Frequently Asked Questions
How does OpenDeepWiki handle the OAuth state parameter for CSRF protection?
The GetAuthorizationUrlAsync method in OAuthService.cs generates a unique state value that is included in the authorization request to the provider. When the provider redirects back to the /api/oauth/{provider}/callback endpoint, this state parameter is validated to ensure the request originated from the same client session, preventing cross-site request forgery attacks.
Can OpenDeepWiki support OAuth providers other than GitHub and Gitee?
Yes. The architecture is provider-agnostic. Administrators can add new providers by inserting a record into the OAuthProvider table with the appropriate AuthorizationUrl, TokenUrl, UserInfoUrl, and optional UserInfoMapping JSON configuration. The OAuthService dynamically loads these settings, so no code changes are required to support additional OAuth 2.0 compliant providers.
What happens if a user already has a local account when they sign in via OAuth?
The FindOrCreateUserAsync method implements a cascading identity resolution strategy. First, it checks for an existing UserOAuth binding by provider and external ID. If none exists, it attempts to match the user by email address from the OAuth provider. If a matching local user is found, it creates the UserOAuth binding to link the accounts. Only if no match is found does it create a new User record with the default "User" role.
How is the JWT token structured after a successful OAuth login?
The JWT is generated by IJwtService.GenerateToken and includes claims for the user's ID, display name, email address, avatar URL, and role names collected by GetUserRolesAsync. The LoginResponse returned to the client contains this AccessToken, an ExpiresIn timestamp, and a UserInfo object with the same identity details, allowing the frontend to immediately update the UI without additional API calls.
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 →