Skip to content

feat: add end user oauth apis#29683

Merged
CourTeous33 merged 2 commits into
feat/end-user-oauthfrom
feat/end-user-oauth-apis
Dec 19, 2025
Merged

feat: add end user oauth apis#29683
CourTeous33 merged 2 commits into
feat/end-user-oauthfrom
feat/end-user-oauth-apis

Conversation

@CourTeous33

Copy link
Copy Markdown
Contributor

Important

  1. Make sure you have read our contribution guidelines
  2. Ensure there is an associated issue and you have been assigned to it
  3. Use the correct syntax to link this PR: Fixes #<issue number>.

Summary

added end user oauth apis

# App-scoped authentication APIs
/api/apps/{app_id}/auth/
├── providers                          # Discover authentication requirements
│   ├── GET                           # List all providers & their auth requirements
│   └── /{provider_id}/               # Provider-specific operations
│       └── credentials               # Credential management for this provider
│           ├── GET                   # List credentials for provider
│           ├── POST                  # Create API key credential
│           └── /{credential_id}
│               ├── GET               # Get credential metadata
│               ├── PATCH             # Update credential (rename, rotate API key)
│               └── DELETE            # Delete credential
└── status                            # Overall authentication status
    └── GET                           # Check what's authenticated

# OAuth endpoints (app-agnostic, works across all apps)
/v1/oauth/
├── {provider}/authorization-url (GET)  # Get OAuth authorization URL
├── callback (GET)                      # OAuth callback handler (redirects to frontend)
└── {provider}/refresh (POST)           # Refresh OAuth token

For details, please check design doc

Screenshots

Before After
... ...

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. 💪 enhancement New feature or request labels Dec 15, 2025
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @CourTeous33, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances Dify's capability by introducing end-user specific authentication for tools. It allows applications to leverage external services that require individual user authorization, such as OAuth-based integrations, directly through the end-user's context. This change provides greater flexibility and personalization for how tools are used within Dify applications, moving beyond workspace-wide credentials to support a more granular, user-centric authentication model.

Highlights

  • End-User OAuth APIs: Introduced new API endpoints under /api/apps/{app_id}/auth/ and /v1/oauth/ to manage end-user authentication for tools, including listing providers, managing credentials (API key and OAuth), initiating OAuth flows, handling callbacks, and refreshing tokens.
  • Tool Authentication Type: Added a new auth_type field to AgentToolEntity and ToolNodeType (frontend) to explicitly define whether a tool uses WORKSPACE or END_USER authentication. The ToolManager now supports resolving credentials based on this auth_type and an end_user_id.
  • OAuth Token Management: Implemented logic within the ToolManager to automatically refresh expired OAuth tokens for end-user credentials during tool runtime, ensuring continuous access to authenticated services.
  • New Services for End-User Auth: Created dedicated services (AppAuthRequirementService, EndUserAuthService, EndUserOAuthService) to handle the business logic for discovering authentication needs, managing end-user credentials, and orchestrating the OAuth process.
  • Workflow Integration: Updated workflow tool nodes to correctly pass the end_user_id and auth_type to the ToolManager when invoking tools, enabling personalized tool execution for end-users within workflows.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive set of APIs and services for end-user OAuth authentication for tools. The changes are well-structured, with new services for managing authentication requirements, credentials, and the OAuth flow itself. The core logic in ToolManager is updated to handle end-user credentials, including on-the-fly token refreshing. The addition of unit tests is also a great step towards ensuring the stability of this new feature.

My review includes suggestions for refactoring to reduce code duplication, improving code style by moving local imports, and enhancing test coverage for the new token refresh mechanism. Overall, this is a solid contribution that significantly enhances the authentication capabilities of the platform.

Comment on lines +246 to +275
if enduser_provider.expires_at != -1 and (enduser_provider.expires_at - 60) < int(time.time()):
from core.plugin.impl.oauth import OAuthHandler
from services.tools.builtin_tools_manage_service import BuiltinToolManageService

# refresh the credentials
tool_provider = ToolProviderID(provider_id)
provider_name = tool_provider.provider_name
# Use the same redirect URI as workspace OAuth to reuse the same OAuth client
redirect_uri = f"{dify_config.CONSOLE_API_URL}/console/api/oauth/plugin/{provider_id}/tool/callback"
system_credentials = BuiltinToolManageService.get_oauth_client(tenant_id, provider_id)

oauth_handler = OAuthHandler()
# refresh the credentials
refreshed_credentials = oauth_handler.refresh_credentials(
tenant_id=tenant_id,
user_id=end_user_id,
plugin_id=tool_provider.plugin_id,
provider=provider_name,
redirect_uri=redirect_uri,
system_credentials=system_credentials or {},
credentials=decrypted_credentials,
)
# update the credentials
enduser_provider.encrypted_credentials = json.dumps(
encrypter.encrypt(refreshed_credentials.credentials)
)
enduser_provider.expires_at = refreshed_credentials.expires_at
db.session.commit()
decrypted_credentials = refreshed_credentials.credentials
cache.delete()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This block of code for refreshing OAuth tokens is very similar to the logic for workspace authentication token refresh on lines 361-390. This duplication can make future maintenance more difficult.

Additionally, the local import of services.tools.builtin_tools_manage_service introduces a circular dependency (tool_manager -> builtin_tools_manage_service -> tool_manager), which is a code smell.

To improve this, consider extracting the common token refresh logic into a private helper method within the ToolManager class. This helper could take parameters like tenant_id, provider_id, user_id, and decrypted_credentials, and return the refreshed credentials. The calling code would then be responsible for updating the specific provider object (enduser_provider or builtin_provider) and committing the changes.

This refactoring would reduce code duplication and centralize the token refresh logic, making the circular dependency more contained and explicit within a single helper method. A long-term solution would involve refactoring the service layers to break the cycle entirely.

def get(self, app_model, provider_id: str):
"""Handle OAuth callback and store credentials."""
# Get OAuth context ID from cookie
context_id = request.cookies.get("oauth_context_id")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This local import should be moved to the top of the file to improve readability and adhere to PEP 8 guidelines.

required_type = None
if supported_types:
# Prefer OAuth2, then API key
from core.plugin.entities.plugin_daemon import CredentialType

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This local import of CredentialType should be moved to the top of the file. It improves readability and adheres to PEP 8 guidelines. There doesn't appear to be a circular dependency that would necessitate a local import here.

# Calculate expiration timestamp
expires_at = -1
if credentials_response.expires_in and credentials_response.expires_in > 0:
import time

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The import time statement is used locally here and on line 213. As time is a standard library, these imports should be moved to the top of the file for better code organization and to follow common Python style practices.

Comment on lines +1 to +5
"""
Unit tests for end-user tool authentication.

Tests the integration of end-user authentication with tool runtime resolution.
"""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The added unit tests cover the basic flows for end-user authentication, which is great. However, the automatic OAuth token refresh logic implemented in tool_manager.py is a critical and complex part of this feature that is not currently tested.

Please consider adding a test case to verify that expired or soon-to-expire OAuth tokens are correctly refreshed on-the-fly when ToolManager.get_tool_runtime is called with auth_type=ToolAuthType.END_USER. This would involve mocking the expires_at field of the EndUserAuthenticationProvider and verifying that the refresh logic is triggered and the credentials are updated.

@CourTeous33
CourTeous33 merged commit 06456af into feat/end-user-oauth Dec 19, 2025
1 check passed
@CourTeous33
CourTeous33 deleted the feat/end-user-oauth-apis branch December 19, 2025 07:30
@laipz8200

laipz8200 commented Dec 19, 2025

Copy link
Copy Markdown
Member

@CourTeous33 Please ensure that the database migration files are applied in a strictly linear, ordered manner, and avoid using the merge function, as it makes downgrades difficult.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💪 enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants