Skip to content

Commit d30c547

Browse files
committed
docs(examples): add comprehensive authentication flow examples
- Add a user login example that automatically selects between browser-based auth and device code flow - Add a client credentials example demonstrating machine-to-machine token fetching and caching - Add a FastAPI server example with bearer token validation and optional scope enforcement - Add an async login example using the device code flow for asynchronous environments Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
1 parent e14228d commit d30c547

4 files changed

Lines changed: 177 additions & 0 deletions

File tree

examples/01_user_login.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Example 1: User login with automatic flow selection.
2+
3+
Tries browser (Auth Code + PKCE) first; falls back to Device Code flow
4+
when no browser is available (e.g., SSH sessions, CI).
5+
6+
Usage:
7+
uv run python examples/01_user_login.py
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import os
13+
14+
import authgate
15+
16+
AUTHGATE_URL = os.environ["AUTHGATE_URL"]
17+
CLIENT_ID = os.environ["CLIENT_ID"]
18+
19+
20+
def main() -> None:
21+
# authenticate() caches the token on disk and refreshes it automatically.
22+
client, token = authgate.authenticate(
23+
AUTHGATE_URL,
24+
CLIENT_ID,
25+
scopes=["openid", "profile", "email"],
26+
)
27+
28+
print(f"Logged in! access_token={token.access_token[:20]}...")
29+
30+
# Use the client to call an OAuth endpoint (e.g., UserInfo).
31+
userinfo = client.userinfo(token.access_token)
32+
print(f"Subject : {userinfo.get('sub')}")
33+
print(f"Email : {userinfo.get('email')}")
34+
35+
36+
if __name__ == "__main__":
37+
main()

examples/02_client_credentials.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Example 2: Machine-to-machine (M2M) with Client Credentials grant.
2+
3+
Tokens are cached in memory and refreshed automatically before expiry.
4+
No user interaction is required.
5+
6+
Usage:
7+
uv run python examples/02_client_credentials.py
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import os
13+
14+
import httpx
15+
16+
from authgate.clientcreds.token_source import TokenSource
17+
from authgate.discovery.client import DiscoveryClient
18+
from authgate.oauth.client import OAuthClient
19+
20+
AUTHGATE_URL = os.environ["AUTHGATE_URL"]
21+
CLIENT_ID = os.environ["AUTHGATE_CLIENT_ID"]
22+
CLIENT_SECRET = os.environ["AUTHGATE_CLIENT_SECRET"]
23+
API_URL = "https://api.example.com/data"
24+
25+
26+
def make_token_source() -> TokenSource:
27+
meta = DiscoveryClient(AUTHGATE_URL).fetch()
28+
client = OAuthClient(CLIENT_ID, meta.to_endpoints(), client_secret=CLIENT_SECRET)
29+
return TokenSource(client, scopes=["api:read"])
30+
31+
32+
def main() -> None:
33+
ts = make_token_source()
34+
35+
# ts.token() returns a cached token and only fetches a new one when needed.
36+
token = ts.token()
37+
print(f"Access token: {token.access_token[:20]}...")
38+
39+
# Attach the token to an outbound HTTP request.
40+
headers = {"Authorization": f"Bearer {token.access_token}"}
41+
resp = httpx.get(API_URL, headers=headers, timeout=10)
42+
print(f"API response: {resp.status_code}")
43+
44+
45+
if __name__ == "__main__":
46+
main()

examples/03_fastapi_server.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Example 3: FastAPI server with Bearer token validation.
2+
3+
Every protected route requires a valid Bearer token issued by AuthGate.
4+
Optional scope enforcement is shown on /admin.
5+
6+
Install extras:
7+
uv pip install authgate[fastapi]
8+
9+
Run:
10+
uv run uvicorn examples.03_fastapi_server:app --reload
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import os
16+
17+
from fastapi import Depends, FastAPI
18+
19+
from authgate.discovery.client import DiscoveryClient
20+
from authgate.middleware.fastapi import BearerAuth
21+
from authgate.middleware.models import TokenInfo
22+
from authgate.oauth.client import OAuthClient
23+
24+
AUTHGATE_URL = os.environ["AUTHGATE_URL"]
25+
CLIENT_ID = os.environ["AUTHGATE_CLIENT_ID"]
26+
27+
# Build shared OAuth client once at startup.
28+
_meta = DiscoveryClient(AUTHGATE_URL).fetch()
29+
_oauth = OAuthClient(CLIENT_ID, _meta.to_endpoints())
30+
31+
# Reusable dependency — validates any Bearer token.
32+
bearer_auth = BearerAuth(_oauth)
33+
34+
# Dependency that also enforces a specific scope.
35+
admin_auth = BearerAuth(_oauth, required_scopes=["admin"])
36+
37+
app = FastAPI(title="AuthGate FastAPI Example")
38+
39+
40+
@app.get("/me")
41+
async def get_me(token_info: TokenInfo = Depends(bearer_auth)) -> dict[str, object]:
42+
"""Return the token's subject and scopes."""
43+
return {
44+
"sub": token_info.sub,
45+
"scopes": token_info.scopes,
46+
"active": token_info.active,
47+
}
48+
49+
50+
@app.get("/admin")
51+
async def admin_endpoint(token_info: TokenInfo = Depends(admin_auth)) -> dict[str, str]:
52+
"""Only accessible with the 'admin' scope."""
53+
return {"message": f"Hello admin {token_info.sub}!"}
54+
55+
56+
@app.get("/health")
57+
async def health() -> dict[str, str]:
58+
"""Public endpoint — no auth required."""
59+
return {"status": "ok"}

examples/04_async_login.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Example 4: Async user login with Device Code flow.
2+
3+
Useful inside async applications (e.g., async CLI tools, Jupyter notebooks).
4+
Auth Code + PKCE is not available in async mode; Device Code is always used.
5+
6+
Usage:
7+
uv run python examples/04_async_login.py
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import asyncio
13+
import os
14+
15+
import authgate
16+
17+
AUTHGATE_URL = os.environ["AUTHGATE_URL"]
18+
CLIENT_ID = os.environ["AUTHGATE_CLIENT_ID"]
19+
20+
21+
async def main() -> None:
22+
client, token = await authgate.async_authenticate(
23+
AUTHGATE_URL,
24+
CLIENT_ID,
25+
scopes=["openid", "profile"],
26+
)
27+
28+
print(f"Logged in! access_token={token.access_token[:20]}...")
29+
30+
userinfo = await client.userinfo(token.access_token)
31+
print(f"Subject: {userinfo.get('sub')}")
32+
33+
34+
if __name__ == "__main__":
35+
asyncio.run(main())

0 commit comments

Comments
 (0)