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_id
  • redirect_uri
  • response_type=code
  • state (CSRF protection token)
  • scope (optional, provider-specific permissions)

Handling the Callback and Token Exchange

The HandleCallbackAsync method executes the critical post-redirect sequence:

  1. ExchangeCodeForTokenAsync – Posts the authorization code to the provider's token endpoint using client_secret authentication. Parses the JSON response into an OAuthTokenResponse containing access_token and optional refresh_token.

  2. GetOAuthUserInfoAsync – Calls the provider's user-info endpoint with the bearer token. Maps the provider-specific JSON structure to a uniform OAuthUserInfo object using either custom UserInfoMapping configuration or built-in defaults for GitHub and Gitee.

  3. FindOrCreateUserAsync – Implements the identity linking strategy:

    • Searches for an existing UserOAuth binding by provider and external ID.
    • If no binding exists, attempts to match by email address.
    • If no user exists, creates a new User record with the default "User" role.
    • Persists a new UserOAuth record linking the local user to the external identity.
  4. GetUserRolesAsync – Retrieves all role names assigned to the user.

  5. 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:

  • ClientId and ClientSecret
  • AuthorizationUrl, TokenUrl, and UserInfoUrl
  • Optional Scope string
  • Optional UserInfoMapping JSON 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 User and OAuthProvider
  • 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 OAuthService class orchestrates the complete lifecycle: URL generation, token exchange, user mapping, and JWT issuance.
  • Provider configuration is dynamic and database-driven via the OAuthProvider entity, 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:

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 →