OAuth Login Flow#
Dify supports OAuth login via GitHub and Google. The implementation lives in api/controllers/console/auth/oauth.py and the OAuth client library in api/libs/oauth.py .
The flow has two endpoints, both under /console/api/oauth/:
| Endpoint | Route | Purpose |
|---|---|---|
OAuthLogin | GET /oauth/login/<provider> | Initiates login; redirects to provider's authorization URL |
OAuthCallback | GET /oauth/authorize/<provider> | Handles the provider callback; creates session and redirects user |
Both endpoints are protected by two decorators:
@setup_requiredβ ensures system setup is complete@social_oauth_login_enabledβ ensures social OAuth login is enabled in system config
Provider instances are constructed in ext_application_services.py via application_services.oauth_providers, with callback URIs pointed at CONSOLE_API_URL + /console/api/oauth/authorize/<provider>.
State Management Across the Provider Redirect#
Because the provider redirect breaks the HTTP session, four pieces of context are serialized into the OAuth state parameter and recovered on callback:
| Field | Description |
|---|---|
invite_token | Workspace invitation token |
timezone | User's preferred timezone |
language | User's preferred interface language |
redirect_url | Page to navigate to after login |
These fields are defined in OAuthState (a TypedDict with total=False, so all fields are optional).
Encoding (encode_oauth_state()): the dict is serialized as compact JSON, UTF-8 encoded, then base64url-encoded with padding stripped. If all fields are absent, None is returned and no state parameter is added to the authorization URL.
Decoding (decode_oauth_state()): padding is restored, then the process is reversed. Any decoding or validation error silently returns an empty dict.
On the login side, OAuthLogin.get() reads the four query params, validates timezone and language, and passes them into get_authorization_url() . GitHub requests the user:email scope ; Google requests openid email .
On the callback side, OAuthCallback.get() calls decode_oauth_state() to recover all four fields before processing the authorization code .
Redirect URL Validation#
After a successful login, the user is redirected to the URL recovered from state. Because the OAuth callback runs on the API origin (CONSOLE_API_URL) while the user's browser belongs to the web origin (CONSOLE_WEB_URL), redirect URLs must be validated carefully.
_safe_console_redirect_target() applies these rules:
- No URL supplied β fall back to
CONSOLE_WEB_URL. - Relative path (no scheme, no authority, doesn't start with
//) β pass through as-is. The browser resolves it against the web origin. - Absolute URL, same origin as
CONSOLE_WEB_URL(same scheme + hostname + port via_url_origin()) β allowed. - Anything else (cross-origin, protocol-relative, malformed port) β fall back to
CONSOLE_WEB_URL.
This design supports split-domain deployments (e.g., api.example.com / app.example.com) β a relative redirect_url like /apps/123 is passed through and the browser appends it to the console web origin automatically.
The oauth_new_user flag is appended as a query parameter before the final redirect so the frontend can show first-time-user onboarding .
Callback Account Resolution & Session Creation#
Inside OAuthCallback.get(), authorization is completed by calling AccountOAuthService.complete_authorization(), which returns AccountSessionTokens or raises specific error types:
- Account resolution: The service resolves the account by provider OpenID or email, or registers a new one.
timezoneandlanguagefrom state are applied to new accounts. - Invite path: If a valid
invite_tokenis present, the identity is linked to the invited account and the user is sent to/signin/invite-settings. This flow is handled throughOAuthInvitationResult. - Standard path: The service returns an
OAuthSignInResultcontainingAccountSessionTokensand anoauth_new_userflag. - Session:
_redirect_with_console_session()takesAccountSessionTokensinstead of anAccountobject, and attaches access, refresh, and CSRF token cookies to the redirect response.
Error cases are handled through specific exception types (OAuthAccountBannedError, OAuthSeatsLimitExceededError, OAuthRegistrationError, OAuthWorkspaceCreationNotAllowedError, InvalidOAuthInvitationError, OAuthInvitationAccountMismatchError) that all redirect to CONSOLE_WEB_URL/signin?message=....
Key Files#
| File | Role |
|---|---|
api/controllers/console/auth/oauth.py | Route handlers: OAuthLogin, OAuthCallback, helper functions |
api/services/account_oauth_service.py | AccountOAuthService β handles OAuth authorization completion and account resolution |
api/extensions/ext_application_services.py | Provider construction via application_services.oauth_providers |
api/libs/oauth.py | OAuthState, encode_oauth_state, decode_oauth_state, GitHubOAuth, GoogleOAuth |
| PR #38900 | Added redirect_url to state; _safe_console_redirect_target() / _url_origin() helpers |