diff --git a/contributing/workflow_samples/auth_config/README.md b/contributing/workflow_samples/auth_api_key/README.md similarity index 100% rename from contributing/workflow_samples/auth_config/README.md rename to contributing/workflow_samples/auth_api_key/README.md diff --git a/contributing/workflow_samples/auth_config/agent.py b/contributing/workflow_samples/auth_api_key/agent.py similarity index 96% rename from contributing/workflow_samples/auth_config/agent.py rename to contributing/workflow_samples/auth_api_key/agent.py index fa0dea93..c98dc55a 100644 --- a/contributing/workflow_samples/auth_config/agent.py +++ b/contributing/workflow_samples/auth_api_key/agent.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Auth Config sample: FunctionNode with API key authentication. +"""Auth API Key sample: FunctionNode with API key authentication. Demonstrates how to use `auth_config` on a FunctionNode to pause the workflow and request user credentials before running the node. @@ -78,6 +78,6 @@ def summarize(node_input: dict): root_agent = Workflow( - name='auth_config', + name='auth_api_key', edges=[('START', fetch_weather, summarize)], ) diff --git a/contributing/workflow_samples/auth_config/tests/go.json b/contributing/workflow_samples/auth_api_key/tests/go.json similarity index 88% rename from contributing/workflow_samples/auth_config/tests/go.json rename to contributing/workflow_samples/auth_api_key/tests/go.json index 51d6f18b..588af648 100644 --- a/contributing/workflow_samples/auth_config/tests/go.json +++ b/contributing/workflow_samples/auth_api_key/tests/go.json @@ -1,5 +1,5 @@ { - "appName": "auth_config", + "appName": "auth_api_key", "events": [ { "author": "user", @@ -18,7 +18,7 @@ } }, { - "author": "auth_config", + "author": "auth_api_key", "content": { "parts": [ { @@ -51,7 +51,7 @@ "fc-1" ], "nodeInfo": { - "path": "auth_config@1/fetch_weather@1" + "path": "auth_api_key@1/fetch_weather@1" } }, { @@ -77,14 +77,14 @@ } }, { - "author": "auth_config", + "author": "auth_api_key", "id": "e-4", "invocationId": "i-1", "nodeInfo": { "outputFor": [ - "auth_config@1/fetch_weather@1" + "auth_api_key@1/fetch_weather@1" ], - "path": "auth_config@1/fetch_weather@1" + "path": "auth_api_key@1/fetch_weather@1" }, "output": { "api_key_used": "1234****", @@ -94,7 +94,7 @@ } }, { - "author": "auth_config", + "author": "auth_api_key", "content": { "parts": [ { @@ -106,7 +106,7 @@ "id": "e-5", "invocationId": "i-1", "nodeInfo": { - "path": "auth_config@1/summarize@1" + "path": "auth_api_key@1/summarize@1" } } ], diff --git a/contributing/workflow_samples/auth_oauth/README.md b/contributing/workflow_samples/auth_oauth/README.md new file mode 100644 index 00000000..bc7abb78 --- /dev/null +++ b/contributing/workflow_samples/auth_oauth/README.md @@ -0,0 +1,111 @@ +# GitHub OAuth Authentication Sample + +## Overview + +This sample demonstrates how to use `AuthConfig` with GitHub OAuth2 on a `FunctionNode` in a workflow. It shows how to pause execution to request a GitHub OAuth token from the user and then use that token to list the user's owned repositories via the GitHub API. + +## Prerequisites + +To run this sample and actually log in, you need to: + +1. **Register an OAuth Application on GitHub**: + * Go to your GitHub account settings. + * Navigate to **Developer settings** > **OAuth Apps** > **New OAuth App**. + * Set the **Homepage URL** and **Authorization callback URL** appropriate for your testing environment (e.g., `http://localhost:8000` if running locally). +2. **Get Credentials**: + * Copy the **Client ID** and **Client Secret**. +3. **Configure Environment Variables**: + * Set the following environment variables in your terminal before running the sample: + ```bash + export GITHUB_CLIENT_ID="your_actual_client_id" + export GITHUB_CLIENT_SECRET="your_actual_client_secret" + ``` + * Alternatively, you can create a `.env` file in the sample directory (`contributing/workflow_samples/auth_oauth/.env`) with the following content: + ```env + GITHUB_CLIENT_ID="your_actual_client_id" + GITHUB_CLIENT_SECRET="your_actual_client_secret" + ``` + The ADK CLI automatically loads `.env` files from the agent directory. + +## Sample Inputs + +- "start" +- "list my repos" + +## Graph + +```mermaid +graph TD + START --> list_github_repos + list_github_repos --> display_result +``` + +## How To + +### 1. Define the AuthConfig for GitHub + +We define an `AuthConfig` that specifies the GitHub OAuth2 endpoints and reads credentials from environment variables. + +```python +auth_config = AuthConfig( + auth_scheme=OAuth2( + flows=OAuthFlows( + authorizationCode=OAuthFlowAuthorizationCode( + authorizationUrl="https://github.com/login/oauth/authorize", + tokenUrl="https://github.com/login/oauth/access_token", + scopes={ + "user": "Read user profile", + "repo": "Access public repositories", + }, + ) + ) + ), + raw_auth_credential=AuthCredential( + auth_type=AuthCredentialTypes.OAUTH2, + oauth2=OAuth2Auth( + client_id=os.environ.get("GITHUB_CLIENT_ID", "YOUR_GITHUB_CLIENT_ID"), + client_secret=os.environ.get("GITHUB_CLIENT_SECRET", "YOUR_GITHUB_CLIENT_SECRET"), + ), + ), + credential_key="github_oauth_token", +) +``` + +### 2. Apply to a Node + +We apply the `auth_config` to the `list_github_repos` node. + +```python +@node(auth_config=auth_config, rerun_on_resume=True) +def list_github_repos(ctx: Context): + # ... +``` + +### 3. Call GitHub API + +Inside the node, we retrieve the token and use the `requests` library to call the GitHub API. + +```python + cred = ctx.get_auth_response(auth_config) + access_token = cred.oauth2.access_token if cred and cred.oauth2 else None + + # ... (headers setup) ... + + response = requests.get("https://api.github.com/user/repos", headers=headers) + repos_data = response.json() + repo_names = [repo["name"] for repo in repos_data] +``` + +## Running the Sample + +To run this sample interactively, use the ADK CLI: + +```bash +adk run contributing/workflow_samples/auth_oauth +``` + +Or use the Web UI: + +```bash +adk web contributing/workflow_samples/ +``` diff --git a/contributing/workflow_samples/auth_oauth/__init__.py b/contributing/workflow_samples/auth_oauth/__init__.py new file mode 100644 index 00000000..02c597e1 --- /dev/null +++ b/contributing/workflow_samples/auth_oauth/__init__.py @@ -0,0 +1 @@ +from . import agent diff --git a/contributing/workflow_samples/auth_oauth/agent.py b/contributing/workflow_samples/auth_oauth/agent.py new file mode 100644 index 00000000..6f57a271 --- /dev/null +++ b/contributing/workflow_samples/auth_oauth/agent.py @@ -0,0 +1,133 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""OAuth Authentication sample: FunctionNode with GitHub OAuth2 token request. + +Demonstrates how to use `auth_config` with GitHub OAuth2 on a FunctionNode to pause +the workflow, request an OAuth token from the user, and use it to list the user's +GitHub repositories. + +Flow: + 1. User sends any message to start the workflow. + 2. The `list_github_repos` node pauses and requests GitHub OAuth credentials. + 3. The user provides the credentials (after logging in to GitHub). + 4. The node runs, calls the GitHub API to list repos, and returns the list. + 5. The `display_result` node displays the repository names. + +Sample queries: + - "start" + - "list my repos" +""" + +import os +from fastapi.openapi.models import OAuth2 +from fastapi.openapi.models import OAuthFlowAuthorizationCode +from fastapi.openapi.models import OAuthFlows +from google.adk import Event +from google.adk import Workflow +from google.adk.agents.context import Context +from google.adk.auth.auth_credential import AuthCredential +from google.adk.auth.auth_credential import AuthCredentialTypes +from google.adk.auth.auth_credential import OAuth2Auth +from google.adk.auth.auth_tool import AuthConfig +from google.adk.workflow import node +import requests + +# --- Auth configuration --- +# Uses GitHub OAuth2 authorization code flow. +# To use this sample, you need to register an OAuth application on GitHub +# and get a Client ID and Client Secret. +# Set the GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET environment variables. +auth_config = AuthConfig( + auth_scheme=OAuth2( + flows=OAuthFlows( + authorizationCode=OAuthFlowAuthorizationCode( + authorizationUrl="https://github.com/login/oauth/authorize", + tokenUrl="https://github.com/login/oauth/access_token", + scopes={ + "user": "Read user profile", + "repo": "Access public repositories", + }, + ) + ) + ), + raw_auth_credential=AuthCredential( + auth_type=AuthCredentialTypes.OAUTH2, + oauth2=OAuth2Auth( + client_id=os.environ.get( + "GITHUB_CLIENT_ID", "YOUR_GITHUB_CLIENT_ID" + ), + client_secret=os.environ.get( + "GITHUB_CLIENT_SECRET", "YOUR_GITHUB_CLIENT_SECRET" + ), + ), + ), + credential_key="github_oauth_token", +) + + +@node(auth_config=auth_config, rerun_on_resume=True) +def list_github_repos(ctx: Context): + """Fetches GitHub repositories for the authenticated user.""" + # After auth completes, the credential is available via ctx. + cred = ctx.get_auth_response(auth_config) + + access_token = ( + cred.oauth2.access_token if cred and cred.oauth2 else None + ) + + if not access_token: + return {"status": "Error", "message": "No access token found"} + + # GitHub API requires a User-Agent header + headers = { + "Authorization": f"Bearer {access_token}", + "User-Agent": "ADK-Sample-Agent", + "Accept": "application/json", + } + + try: + response = requests.get( + "https://api.github.com/user/repos", headers=headers + ) + response.raise_for_status() + repos_data = response.json() + # Extract repo names + repo_names = [repo["name"] for repo in repos_data] + return { + "status": "Success", + "repos": repo_names, + } + except Exception as e: + return { + "status": "Error", + "message": f"Failed to fetch repos: {e}", + } + + +def display_result(node_input: dict): + """Displays the result of accessing the resource.""" + if node_input["status"] == "Success": + repos_str = ", ".join(node_input["repos"]) + yield Event(message=f"Successfully fetched repositories: {repos_str}") + else: + yield Event( + message=f"Failed to fetch repositories. Error: {node_input.get('message', 'Unknown error')}" + ) + + +root_agent = Workflow( + name="auth_oauth", + edges=[("START", list_github_repos, display_result)], +)