From 4e2b3928d90f43367b1b4fd115fdae16d554f565 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 19 Dec 2025 10:30:45 -0800 Subject: [PATCH 1/3] docs(api-clients): add OAuth authentication examples for all SDK languages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add comprehensive OAuth authentication documentation to all four API client libraries (Go, Java, Python, TypeScript). Each includes: - Prerequisites and admin configuration links - Required headers table (Authorization, X-Glean-Auth-Type) - Complete authorization code flow example using idiomatic libraries - Token refresh tips for production use 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/libraries/api-clients/go.mdx | 104 ++++++++++++++++++++++ docs/libraries/api-clients/java.mdx | 104 ++++++++++++++++++++++ docs/libraries/api-clients/python.mdx | 75 ++++++++++++++++ docs/libraries/api-clients/typescript.mdx | 82 +++++++++++++++++ 4 files changed, 365 insertions(+) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 05227060..137219b6 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -265,6 +265,110 @@ response, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{ }) ``` +### OAuth Authentication + +OAuth allows you to use access tokens from your identity provider (Google, Azure, Okta, etc.) instead of Glean-issued tokens. + +:::info Prerequisites +- OAuth enabled in [Glean Admin > Third-Party OAuth](https://app.glean.com/admin/setup/third-party-oauth) +- Your OAuth Client ID registered with Glean +- See [OAuth Setup Guide](https://docs.glean.com/administration/oauth/oauth-idp) for admin configuration +::: + +OAuth requests require these headers: + +| Header | Value | +|--------|-------| +| `Authorization` | `Bearer ` | +| `X-Glean-Auth-Type` | `OAUTH` | + +#### Example: Authorization Code Flow + +This example uses [golang.org/x/oauth2](https://pkg.go.dev/golang.org/x/oauth2): + +```go +package main + +import ( + "context" + "encoding/json" + "net/http" + "os" + + "golang.org/x/oauth2" + glean "github.com/gleanwork/api-client-go" +) + +var oauthConfig = &oauth2.Config{ + ClientID: os.Getenv("OAUTH_CLIENT_ID"), + ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), + RedirectURL: "http://localhost:8080/callback", + Scopes: []string{"openid", "email"}, + Endpoint: oauth2.Endpoint{ + AuthURL: os.Getenv("OAUTH_AUTH_URL"), + TokenURL: os.Getenv("OAUTH_TOKEN_URL"), + }, +} + +// oauthTransport adds OAuth headers to all requests +type oauthTransport struct { + token string + transport http.RoundTripper +} + +func (t *oauthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("Authorization", "Bearer "+t.token) + req.Header.Set("X-Glean-Auth-Type", "OAUTH") + return t.transport.RoundTrip(req) +} + +func main() { + http.HandleFunc("/login", handleLogin) + http.HandleFunc("/callback", handleCallback) + http.ListenAndServe(":8080", nil) +} + +func handleLogin(w http.ResponseWriter, r *http.Request) { + url := oauthConfig.AuthCodeURL("state") + http.Redirect(w, r, url, http.StatusTemporaryRedirect) +} + +func handleCallback(w http.ResponseWriter, r *http.Request) { + code := r.URL.Query().Get("code") + token, err := oauthConfig.Exchange(context.Background(), code) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Create HTTP client with OAuth headers + httpClient := &http.Client{ + Transport: &oauthTransport{ + token: token.AccessToken, + transport: http.DefaultTransport, + }, + } + + // Create Glean client with custom HTTP client + client := glean.New( + glean.WithInstance(os.Getenv("GLEAN_INSTANCE")), + glean.WithClient(httpClient), + ) + + results, err := client.Client.Search.Query(r.Context(), "quarterly reports", nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(results) +} +``` + +:::tip +Access tokens typically expire after ~1 hour. For production use, use `oauth2.Config.TokenSource` for automatic refresh. +::: + ## Error Handling ```go diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index c376b23c..b7ddb87b 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -230,6 +230,110 @@ glean: instance: ${GLEAN_INSTANCE} ``` +### OAuth Authentication + +OAuth allows you to use access tokens from your identity provider (Google, Azure, Okta, etc.) instead of Glean-issued tokens. + +:::info Prerequisites +- OAuth enabled in [Glean Admin > Third-Party OAuth](https://app.glean.com/admin/setup/third-party-oauth) +- Your OAuth Client ID registered with Glean +- See [OAuth Setup Guide](https://docs.glean.com/administration/oauth/oauth-idp) for admin configuration +::: + +OAuth requests require these headers: + +| Header | Value | +|--------|-------| +| `Authorization` | `Bearer ` | +| `X-Glean-Auth-Type` | `OAUTH` | + +#### Example: Authorization Code Flow with Spring Security + +This example uses Spring Security OAuth2 Client: + +```yaml +# application.yml +spring: + security: + oauth2: + client: + registration: + provider: + client-id: ${OAUTH_CLIENT_ID} + client-secret: ${OAUTH_CLIENT_SECRET} + scope: openid,email + provider: + provider: + issuer-uri: ${OAUTH_ISSUER} + +glean: + instance: ${GLEAN_INSTANCE} +``` + +```java +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.web.bind.annotation.*; +import com.glean.api_client.glean_api_client.Glean; +import com.glean.api_client.glean_api_client.models.components.*; +import com.glean.api_client.glean_api_client.utils.HTTPClient; +import com.glean.api_client.glean_api_client.utils.Utils; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.Map; + +@RestController +public class GleanOAuthController { + + @Value("${glean.instance}") + private String gleanInstance; + + @GetMapping("/search") + public Map search( + @RegisteredOAuth2AuthorizedClient("provider") OAuth2AuthorizedClient oauthClient, + @RequestParam String query) throws Exception { + + String accessToken = oauthClient.getAccessToken().getTokenValue(); + + // Create HTTP client that adds OAuth headers + HTTPClient httpClient = new HTTPClient() { + private final HTTPClient defaultClient = new HTTPClient(); + + @Override + public HttpResponse send(HttpRequest request) + throws java.io.IOException, InterruptedException, java.net.URISyntaxException { + HttpRequest modifiedRequest = Utils.copy(request) + .header("Authorization", "Bearer " + accessToken) + .header("X-Glean-Auth-Type", "OAUTH") + .build(); + return defaultClient.send(modifiedRequest); + } + }; + + // Create Glean client with custom HTTP client + Glean glean = Glean.builder() + .instance(gleanInstance) + .client(httpClient) + .build(); + + var response = glean.client().search().query() + .query(query) + .pageSize(10) + .call(); + + return Map.of("results", response.searchResponse() + .map(SearchResponse::results) + .orElse(List.of())); + } +} +``` + +:::tip +Access tokens typically expire after ~1 hour. Spring Security OAuth2 Client handles token refresh automatically when configured with a refresh token. +::: + ## Error Handling ```java diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index 8bb21c43..bddc64d2 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -208,6 +208,81 @@ response = client.client.chat.create( ) ``` +### OAuth Authentication + +OAuth allows you to use access tokens from your identity provider (Google, Azure, Okta, etc.) instead of Glean-issued tokens. + +:::info Prerequisites +- OAuth enabled in [Glean Admin > Third-Party OAuth](https://app.glean.com/admin/setup/third-party-oauth) +- Your OAuth Client ID registered with Glean +- See [OAuth Setup Guide](https://docs.glean.com/administration/oauth/oauth-idp) for admin configuration +::: + +OAuth requests require these headers: + +| Header | Value | +|--------|-------| +| `Authorization` | `Bearer ` | +| `X-Glean-Auth-Type` | `OAUTH` | + +#### Example: Authorization Code Flow + +This example uses [Authlib](https://pypi.org/project/Authlib/) with Flask: + +```python +import os +import httpx +from flask import Flask, redirect, request, jsonify +from authlib.integrations.flask_client import OAuth +from glean.api_client import Glean + +app = Flask(__name__) +app.secret_key = os.urandom(24) + +oauth = OAuth(app) +oauth.register( + name='provider', + client_id=os.getenv('OAUTH_CLIENT_ID'), + client_secret=os.getenv('OAUTH_CLIENT_SECRET'), + server_metadata_url=os.getenv('OAUTH_ISSUER') + '/.well-known/openid-configuration', + client_kwargs={'scope': 'openid email'}, +) + +@app.route('/login') +def login(): + redirect_uri = 'http://localhost:5000/callback' + return oauth.provider.authorize_redirect(redirect_uri) + +@app.route('/callback') +def callback(): + token = oauth.provider.authorize_access_token() + + # Create HTTP client with OAuth headers + http_client = httpx.Client(headers={ + 'Authorization': f"Bearer {token['access_token']}", + 'X-Glean-Auth-Type': 'OAUTH', + }) + + # Use OAuth token with Glean + with Glean( + instance=os.getenv('GLEAN_INSTANCE'), + client=http_client, + ) as glean: + results = glean.client.search.query( + query='quarterly reports', + page_size=10, + ) + + return jsonify(results.to_dict()) + +if __name__ == '__main__': + app.run(port=5000) +``` + +:::tip +Access tokens typically expire after ~1 hour. For production use, implement token refresh using `token['refresh_token']`. +::: + ## Error Handling ```python diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 334704b7..cc6cc05a 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -196,6 +196,88 @@ const response = await client.client.chat.create({ }); ``` +### OAuth Authentication + +OAuth allows you to use access tokens from your identity provider (Google, Azure, Okta, etc.) instead of Glean-issued tokens. + +:::info Prerequisites +- OAuth enabled in [Glean Admin > Third-Party OAuth](https://app.glean.com/admin/setup/third-party-oauth) +- Your OAuth Client ID registered with Glean +- See [OAuth Setup Guide](https://docs.glean.com/administration/oauth/oauth-idp) for admin configuration +::: + +OAuth requests require these headers: + +| Header | Value | +|--------|-------| +| `Authorization` | `Bearer ` | +| `X-Glean-Auth-Type` | `OAUTH` | + +#### Example: Authorization Code Flow + +This example uses [openid-client](https://www.npmjs.com/package/openid-client) with Express: + +```typescript +import express from 'express'; +import * as client from 'openid-client'; +import { Glean } from '@gleanwork/api-client'; + +const config = { + clientId: process.env.OAUTH_CLIENT_ID!, + clientSecret: process.env.OAUTH_CLIENT_SECRET!, + redirectUri: 'http://localhost:3000/callback', + issuer: process.env.OAUTH_ISSUER!, // e.g., https://accounts.google.com +}; + +const app = express(); +let oidcConfig: client.Configuration; + +// Initialize OIDC client +async function init() { + oidcConfig = await client.discovery( + new URL(config.issuer), + config.clientId, + config.clientSecret + ); +} + +app.get('/login', (req, res) => { + const authUrl = client.buildAuthorizationUrl(oidcConfig, { + redirect_uri: config.redirectUri, + scope: 'openid email', + }); + res.redirect(authUrl.href); +}); + +app.get('/callback', async (req, res) => { + const callbackUrl = new URL(req.url, `http://${req.headers.host}`); + const tokens = await client.authorizationCodeGrant(oidcConfig, callbackUrl, { + expectedState: client.skipStateCheck, + }); + + // Use OAuth token with Glean + const glean = new Glean({ instance: process.env.GLEAN_INSTANCE! }); + + const results = await glean.client.search.search({ + query: 'quarterly reports', + pageSize: 10, + }, { + headers: { + 'Authorization': `Bearer ${tokens.access_token}`, + 'X-Glean-Auth-Type': 'OAUTH', + }, + }); + + res.json(results); +}); + +init().then(() => app.listen(3000)); +``` + +:::tip +Access tokens typically expire after ~1 hour. For production use, implement token refresh using `tokens.refresh_token`. +::: + ## Error Handling ```typescript From 0ebbdfc3fed83aa8f558e70786abebd8692c1853 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 19 Dec 2025 11:04:00 -0800 Subject: [PATCH 2/3] fix(oauth-docs): correct OAuth examples with proper security practices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review feedback: - TypeScript: Add PKCE (code_challenge, code_verifier) and state verification using openid-client v6 correct API - Java: Fix Spring Security YAML structure with proper named registration/provider ("glean") and authorization-grant-type - Go: Add cryptographic state generation and validation to prevent CSRF attacks (was using hardcoded "state") 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/libraries/api-clients/go.mdx | 42 ++++++++++++++++++++++- docs/libraries/api-clients/java.mdx | 37 +++++++------------- docs/libraries/api-clients/typescript.mdx | 27 ++++++++++++--- 3 files changed, 76 insertions(+), 30 deletions(-) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 137219b6..a6f8bfa0 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -291,9 +291,12 @@ package main import ( "context" + "crypto/rand" + "encoding/base64" "encoding/json" "net/http" "os" + "sync" "golang.org/x/oauth2" glean "github.com/gleanwork/api-client-go" @@ -310,6 +313,20 @@ var oauthConfig = &oauth2.Config{ }, } +// In-memory state store (use Redis/database in production) +var ( + stateStore = make(map[string]bool) + stateMu sync.Mutex +) + +func generateState() (string, error) { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(b), nil +} + // oauthTransport adds OAuth headers to all requests type oauthTransport struct { token string @@ -329,11 +346,34 @@ func main() { } func handleLogin(w http.ResponseWriter, r *http.Request) { - url := oauthConfig.AuthCodeURL("state") + state, err := generateState() + if err != nil { + http.Error(w, "Failed to generate state", http.StatusInternalServerError) + return + } + + // Store state for CSRF validation + stateMu.Lock() + stateStore[state] = true + stateMu.Unlock() + + url := oauthConfig.AuthCodeURL(state) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func handleCallback(w http.ResponseWriter, r *http.Request) { + // Validate state to prevent CSRF attacks + state := r.URL.Query().Get("state") + stateMu.Lock() + valid := stateStore[state] + delete(stateStore, state) + stateMu.Unlock() + + if !valid { + http.Error(w, "Invalid state parameter", http.StatusBadRequest) + return + } + code := r.URL.Query().Get("code") token, err := oauthConfig.Exchange(context.Background(), code) if err != nil { diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index b7ddb87b..6c46141f 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -258,12 +258,14 @@ spring: oauth2: client: registration: - provider: + glean: + provider: glean client-id: ${OAUTH_CLIENT_ID} client-secret: ${OAUTH_CLIENT_SECRET} scope: openid,email + authorization-grant-type: authorization_code provider: - provider: + glean: issuer-uri: ${OAUTH_ISSUER} glean: @@ -277,10 +279,6 @@ import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2Aut import org.springframework.web.bind.annotation.*; import com.glean.api_client.glean_api_client.Glean; import com.glean.api_client.glean_api_client.models.components.*; -import com.glean.api_client.glean_api_client.utils.HTTPClient; -import com.glean.api_client.glean_api_client.utils.Utils; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.util.List; import java.util.Map; @@ -292,36 +290,25 @@ public class GleanOAuthController { @GetMapping("/search") public Map search( - @RegisteredOAuth2AuthorizedClient("provider") OAuth2AuthorizedClient oauthClient, + @RegisteredOAuth2AuthorizedClient("glean") OAuth2AuthorizedClient oauthClient, @RequestParam String query) throws Exception { String accessToken = oauthClient.getAccessToken().getTokenValue(); - // Create HTTP client that adds OAuth headers - HTTPClient httpClient = new HTTPClient() { - private final HTTPClient defaultClient = new HTTPClient(); - - @Override - public HttpResponse send(HttpRequest request) - throws java.io.IOException, InterruptedException, java.net.URISyntaxException { - HttpRequest modifiedRequest = Utils.copy(request) - .header("Authorization", "Bearer " + accessToken) - .header("X-Glean-Auth-Type", "OAUTH") - .build(); - return defaultClient.send(modifiedRequest); - } - }; - - // Create Glean client with custom HTTP client + // Create Glean client and pass OAuth headers per-request Glean glean = Glean.builder() .instance(gleanInstance) - .client(httpClient) .build(); var response = glean.client().search().query() .query(query) .pageSize(10) - .call(); + .call(new com.glean.api_client.glean_api_client.utils.Options() {{ + headers(Map.of( + "Authorization", "Bearer " + accessToken, + "X-Glean-Auth-Type", "OAUTH" + )); + }}); return Map.of("results", response.searchResponse() .map(SearchResponse::results) diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index cc6cc05a..349c5233 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -215,10 +215,11 @@ OAuth requests require these headers: #### Example: Authorization Code Flow -This example uses [openid-client](https://www.npmjs.com/package/openid-client) with Express: +This example uses [openid-client](https://github.com/panva/openid-client) (v6+) with Express: ```typescript import express from 'express'; +import session from 'express-session'; import * as client from 'openid-client'; import { Glean } from '@gleanwork/api-client'; @@ -230,9 +231,11 @@ const config = { }; const app = express(); +app.use(session({ secret: 'session-secret', resave: false, saveUninitialized: false })); + let oidcConfig: client.Configuration; -// Initialize OIDC client +// Initialize OIDC client on startup async function init() { oidcConfig = await client.discovery( new URL(config.issuer), @@ -241,18 +244,34 @@ async function init() { ); } -app.get('/login', (req, res) => { +app.get('/login', async (req, res) => { + // Generate PKCE code verifier and state for security + const codeVerifier = client.randomPKCECodeVerifier(); + const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier); + const state = client.randomState(); + + // Store in session for verification on callback + req.session.codeVerifier = codeVerifier; + req.session.state = state; + const authUrl = client.buildAuthorizationUrl(oidcConfig, { redirect_uri: config.redirectUri, scope: 'openid email', + code_challenge: codeChallenge, + code_challenge_method: 'S256', + state, }); res.redirect(authUrl.href); }); app.get('/callback', async (req, res) => { + const { codeVerifier, state } = req.session; const callbackUrl = new URL(req.url, `http://${req.headers.host}`); + + // Exchange code for tokens with PKCE and state verification const tokens = await client.authorizationCodeGrant(oidcConfig, callbackUrl, { - expectedState: client.skipStateCheck, + pkceCodeVerifier: codeVerifier, + expectedState: state, }); // Use OAuth token with Glean From 98d1385a66d04dc09b522fa6df42c8b6f40411a4 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Fri, 19 Dec 2025 11:57:06 -0800 Subject: [PATCH 3/3] fix(oauth-docs): use verified SDK APIs and add PKCE support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Go: Use correct Search.Query() with components.SearchRequest, add PKCE with oauth2.GenerateVerifier/S256ChallengeOption/VerifierOption - Java: Add Spring Security PKCE config, use correct search().query() API - Python: Add PKCE via code_challenge_method='S256', use models.SearchRequest - TypeScript: Use correct search.query() API (not search.search()) All examples now use real SDK APIs verified from GitHub READMEs and include proper PKCE implementation for secure OAuth flows. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docs/libraries/api-clients/go.mdx | 38 +++++++----- docs/libraries/api-clients/java.mdx | 73 ++++++++++++++++++++--- docs/libraries/api-clients/python.mdx | 8 ++- docs/libraries/api-clients/typescript.mdx | 2 +- 4 files changed, 94 insertions(+), 27 deletions(-) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index a6f8bfa0..918b62b7 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -300,6 +300,7 @@ import ( "golang.org/x/oauth2" glean "github.com/gleanwork/api-client-go" + "github.com/gleanwork/api-client-go/models/components" ) var oauthConfig = &oauth2.Config{ @@ -313,10 +314,10 @@ var oauthConfig = &oauth2.Config{ }, } -// In-memory state store (use Redis/database in production) +// In-memory store for state and PKCE verifier (use Redis/database in production) var ( - stateStore = make(map[string]bool) - stateMu sync.Mutex + authStore = make(map[string]string) // state -> verifier + authMu sync.Mutex ) func generateState() (string, error) { @@ -352,30 +353,35 @@ func handleLogin(w http.ResponseWriter, r *http.Request) { return } - // Store state for CSRF validation - stateMu.Lock() - stateStore[state] = true - stateMu.Unlock() + // Generate PKCE code verifier + verifier := oauth2.GenerateVerifier() - url := oauthConfig.AuthCodeURL(state) + // Store state and verifier for callback validation + authMu.Lock() + authStore[state] = verifier + authMu.Unlock() + + // Include PKCE challenge in authorization URL + url := oauthConfig.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier)) http.Redirect(w, r, url, http.StatusTemporaryRedirect) } func handleCallback(w http.ResponseWriter, r *http.Request) { - // Validate state to prevent CSRF attacks + // Validate state and retrieve PKCE verifier state := r.URL.Query().Get("state") - stateMu.Lock() - valid := stateStore[state] - delete(stateStore, state) - stateMu.Unlock() + authMu.Lock() + verifier, valid := authStore[state] + delete(authStore, state) + authMu.Unlock() if !valid { http.Error(w, "Invalid state parameter", http.StatusBadRequest) return } + // Exchange code for token with PKCE verifier code := r.URL.Query().Get("code") - token, err := oauthConfig.Exchange(context.Background(), code) + token, err := oauthConfig.Exchange(context.Background(), code, oauth2.VerifierOption(verifier)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -395,7 +401,9 @@ func handleCallback(w http.ResponseWriter, r *http.Request) { glean.WithClient(httpClient), ) - results, err := client.Client.Search.Query(r.Context(), "quarterly reports", nil) + results, err := client.Client.Search.Query(r.Context(), components.SearchRequest{ + Query: "quarterly reports", + }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index 6c46141f..4f8691d3 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -272,6 +272,44 @@ glean: instance: ${GLEAN_INSTANCE} ``` +```java +// SecurityConfig.java - Enable PKCE for confidential clients +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestCustomizers; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http, + ClientRegistrationRepository clientRegistrationRepository) throws Exception { + + // Create resolver with PKCE enabled + OAuth2AuthorizationRequestResolver pkceResolver = + pkceAuthorizationRequestResolver(clientRegistrationRepository); + + http.oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(auth -> auth + .authorizationRequestResolver(pkceResolver))); + + return http.build(); + } + + private OAuth2AuthorizationRequestResolver pkceAuthorizationRequestResolver( + ClientRegistrationRepository repo) { + var resolver = new DefaultOAuth2AuthorizationRequestResolver(repo, "/oauth2/authorization"); + resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); + return resolver; + } +} +``` + ```java import org.springframework.beans.factory.annotation.Value; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; @@ -279,6 +317,10 @@ import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2Aut import org.springframework.web.bind.annotation.*; import com.glean.api_client.glean_api_client.Glean; import com.glean.api_client.glean_api_client.models.components.*; +import com.glean.api_client.glean_api_client.utils.HTTPClient; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.util.List; import java.util.Map; @@ -295,20 +337,33 @@ public class GleanOAuthController { String accessToken = oauthClient.getAccessToken().getTokenValue(); - // Create Glean client and pass OAuth headers per-request + // Create custom HTTP client that adds OAuth headers + HTTPClient oauthHttpClient = new HTTPClient() { + private final HttpClient client = HttpClient.newHttpClient(); + + @Override + public HttpResponse send(HttpRequest request) + throws java.io.IOException, InterruptedException { + HttpRequest oauthRequest = HttpRequest.newBuilder(request, (n, v) -> true) + .header("Authorization", "Bearer " + accessToken) + .header("X-Glean-Auth-Type", "OAUTH") + .build(); + return client.send(oauthRequest, HttpResponse.BodyHandlers.ofInputStream()); + } + }; + + // Create Glean client with custom HTTP client Glean glean = Glean.builder() .instance(gleanInstance) + .client(oauthHttpClient) .build(); var response = glean.client().search().query() - .query(query) - .pageSize(10) - .call(new com.glean.api_client.glean_api_client.utils.Options() {{ - headers(Map.of( - "Authorization", "Bearer " + accessToken, - "X-Glean-Auth-Type", "OAUTH" - )); - }}); + .searchRequest(SearchRequest.builder() + .query(query) + .pageSize(10) + .build()) + .call(); return Map.of("results", response.searchResponse() .map(SearchResponse::results) diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index bddc64d2..ca9afe74 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -235,6 +235,7 @@ import httpx from flask import Flask, redirect, request, jsonify from authlib.integrations.flask_client import OAuth from glean.api_client import Glean +from glean.api_client import models app = Flask(__name__) app.secret_key = os.urandom(24) @@ -246,6 +247,7 @@ oauth.register( client_secret=os.getenv('OAUTH_CLIENT_SECRET'), server_metadata_url=os.getenv('OAUTH_ISSUER') + '/.well-known/openid-configuration', client_kwargs={'scope': 'openid email'}, + code_challenge_method='S256', # Enable PKCE ) @app.route('/login') @@ -269,8 +271,10 @@ def callback(): client=http_client, ) as glean: results = glean.client.search.query( - query='quarterly reports', - page_size=10, + search_request=models.SearchRequest( + query='quarterly reports', + page_size=10, + ) ) return jsonify(results.to_dict()) diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 349c5233..dfabe83c 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -277,7 +277,7 @@ app.get('/callback', async (req, res) => { // Use OAuth token with Glean const glean = new Glean({ instance: process.env.GLEAN_INSTANCE! }); - const results = await glean.client.search.search({ + const results = await glean.client.search.query({ query: 'quarterly reports', pageSize: 10, }, {