From bf460efee8a3e9264938b98b64877b1554842239 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Mon, 15 Jun 2026 12:32:47 -0700 Subject: [PATCH 1/6] docs(api-clients): document OAuth access tokens for all SDK languages An OAuth access token is a bearer credential, so it goes in the existing apiToken field. Add an OAuth section to each SDK's Authentication docs covering the three tiers (Glean token; Glean-issued / Authorization Server OAuth, incl. DCR; external-IdP OAuth + the X-Glean-Auth-Type: OAUTH header) using each SDK's verified per-request header mechanism. Java uses a small custom HTTPClient since its builder has no per-request header option. Also correct two stale ActAs examples in the same sections: Python headers= -> http_headers=, and Go context.WithValue -> operations.WithSetHeaders. Supersedes the earlier draft of this PR, which injected both headers via a full custom HTTP client in every language. Pairs with gleanwork/open-api#111. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/libraries/api-clients/go.mdx | 45 +++++++++++++++++++---- docs/libraries/api-clients/java.mdx | 41 +++++++++++++++++++++ docs/libraries/api-clients/python.mdx | 24 +++++++++++- docs/libraries/api-clients/typescript.mdx | 23 ++++++++++++ 4 files changed, 124 insertions(+), 9 deletions(-) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 21eb9c388..60e080fa1 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -248,21 +248,50 @@ client := glean.New( ### Global Tokens with ActAs -```go -ctx := context.Background() +Per-request headers are passed with `operations.WithSetHeaders`: -// Add ActAs header to context -ctx = context.WithValue(ctx, "X-Glean-ActAs", "user@company.com") +```go +import "github.com/gleanwork/api-client-go/models/operations" -response, err := client.Client.Chat.Create(ctx, &glean.ChatRequest{ - Messages: []glean.ChatMessage{{ - Fragments: []glean.ChatMessageFragment{{ +response, err := client.Client.Chat.Create(ctx, components.ChatRequest{ + Messages: []components.ChatMessage{{ + Fragments: []components.ChatMessageFragment{{ Text: "Hello", }}, }}, -}) +}, nil, operations.WithSetHeaders(map[string]string{ + "X-Glean-ActAs": "user@company.com", +})) +``` + +### OAuth Access Tokens + +An OAuth access token is a bearer credential, so it goes in the same `WithAPIToken` option: + +```go +client := glean.New( + glean.WithAPIToken(oauthAccessToken), + glean.WithServerURL("https://your-server-id-be.glean.com"), +) ``` +Tokens issued by the **Glean OAuth Authorization Server** (including tokens obtained via Dynamic Client Registration) are detected automatically. Tokens issued by an **external identity provider** (Google, Okta, Azure, etc.) additionally require the `X-Glean-Auth-Type: OAUTH` header on each request: + +```go +import ( + "github.com/gleanwork/api-client-go/models/components" + "github.com/gleanwork/api-client-go/models/operations" +) + +results, err := client.Client.Search.Query(ctx, &components.SearchRequest{ + Query: "quarterly reports", +}, operations.WithSetHeaders(map[string]string{ + "X-Glean-Auth-Type": "OAUTH", +})) +``` + +See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. + ## Error Handling ```go diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index 49313cd07..4c190f085 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -228,6 +228,47 @@ glean: server-url: ${GLEAN_SERVER_URL} ``` +### OAuth Access Tokens + +An OAuth access token is a bearer credential, so it goes in the same `apiToken` field: + +```java +Glean client = Glean.builder() + .apiToken(oauthAccessToken) + .serverURL("https://your-server-id-be.glean.com") + .build(); +``` + +Tokens issued by the **Glean OAuth Authorization Server** (including tokens obtained via Dynamic Client Registration) are detected automatically and need no extra header. + +Tokens issued by an **external identity provider** (Google, Okta, Azure, etc.) additionally require the `X-Glean-Auth-Type: OAUTH` header on each request. The builder has no per-request header option, so supply a custom HTTP client that adds it: + +```java +import com.glean.api_client.glean_api_client.utils.HTTPClient; +import com.glean.api_client.glean_api_client.utils.SpeakeasyHTTPClient; +import java.io.InputStream; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +HTTPClient oauthClient = new SpeakeasyHTTPClient() { + @Override + public HttpResponse send(HttpRequest request) + throws java.io.IOException, InterruptedException, java.net.URISyntaxException { + return super.send(HttpRequest.newBuilder(request, (name, value) -> true) + .header("X-Glean-Auth-Type", "OAUTH") + .build()); + } +}; + +Glean client = Glean.builder() + .apiToken(oauthAccessToken) + .serverURL("https://your-server-id-be.glean.com") + .client(oauthClient) + .build(); +``` + +See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. + ## Error Handling ```java diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index 53d8d6aa8..5762700d5 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -202,10 +202,32 @@ client = Glean( ```python response = client.client.chat.create( messages=[{"fragments": [{"text": "Hello"}]}], - headers={"X-Glean-ActAs": "user@company.com"} + http_headers={"X-Glean-ActAs": "user@company.com"} ) ``` +### OAuth Access Tokens + +An OAuth access token is a bearer credential, so it goes in the same `api_token` field: + +```python +client = Glean( + api_token=oauth_access_token, + server_url="https://your-server-id-be.glean.com", +) +``` + +Tokens issued by the **Glean OAuth Authorization Server** (including tokens obtained via Dynamic Client Registration) are detected automatically. Tokens issued by an **external identity provider** (Google, Okta, Azure, etc.) additionally require the `X-Glean-Auth-Type: OAUTH` header on each request: + +```python +response = client.client.chat.create( + messages=[{"fragments": [{"text": "Hello"}]}], + http_headers={"X-Glean-Auth-Type": "OAUTH"} +) +``` + +See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. + ## Error Handling ```python diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 398d7056f..7cbf375d1 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -194,6 +194,29 @@ const response = await client.client.chat.create({ }); ``` +### OAuth Access Tokens + +An OAuth access token is a bearer credential, so it goes in the same `apiToken` field — no separate option is needed: + +```typescript +const client = new Glean({ + apiToken: oauthAccessToken, + serverURL: "https://your-server-id-be.glean.com", +}); +``` + +Tokens issued by the **Glean OAuth Authorization Server** (including tokens obtained via Dynamic Client Registration) are detected automatically. Tokens issued by an **external identity provider** (Google, Okta, Azure, etc.) additionally require the `X-Glean-Auth-Type: OAUTH` header on each request: + +```typescript +const response = await client.client.chat.create({ + messages: [{ fragments: [{ text: "Hello" }] }] +}, { + headers: { "X-Glean-Auth-Type": "OAUTH" } +}); +``` + +See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. + ## Error Handling ```typescript From 9491855b5b2943603e6f008820a84cf5ac54a7e6 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Mon, 15 Jun 2026 13:03:19 -0700 Subject: [PATCH 2/6] docs(client-auth): reflect Glean Authorization Server + IdP OAuth model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the OAuth guide around the two token sources: - Glean OAuth Authorization Server (OAuth 2.1, PKCE, DCR) — detected by issuer, no X-Glean-Auth-Type header - External IdP tokens — require X-Glean-Auth-Type: OAUTH Make the header requirement conditional throughout (headers table, examples, troubleshooting), link out to the SDK OAuth examples and the remote MCP server, and align the authentication overview page. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/api-info/client/authentication/oauth.mdx | 239 +++++++++--------- .../client/authentication/overview.mdx | 14 +- 2 files changed, 124 insertions(+), 129 deletions(-) diff --git a/docs/api-info/client/authentication/oauth.mdx b/docs/api-info/client/authentication/oauth.mdx index 40c904a6e..2ac73cf8f 100644 --- a/docs/api-info/client/authentication/oauth.mdx +++ b/docs/api-info/client/authentication/oauth.mdx @@ -1,7 +1,7 @@ --- title: OAuth Authentication sidebar_label: OAuth -description: Complete OAuth setup guide for Client API authentication with provider-specific instructions +description: Use OAuth access tokens with the Client API — from the Glean OAuth Authorization Server or your external identity provider --- import Tabs from '@theme/Tabs'; @@ -9,90 +9,132 @@ import TabItem from '@theme/TabItem'; # OAuth Authentication for Client API -OAuth is the **recommended authentication method** for Client API integrations. It allows you to use access tokens from your existing identity provider without managing additional tokens. +OAuth is the **recommended authentication method** for Client API integrations. You authenticate with an OAuth access token instead of managing a Glean-issued API token. - - - **No token management** - Use existing identity provider tokens - - **Full API access** - No scope restrictions like Glean tokens - - **Provider flexibility** - Works with Google, Azure, Okta, OneLogin - - **Enterprise security** - Leverages your existing auth infrastructure - +There are two sources for that token, and they behave slightly differently on the wire: + + + + **Glean issues the tokens (OAuth 2.1)** + + - Authorization Code flow with PKCE + - Static clients or [Dynamic Client Registration](https://docs.glean.com/administration/oauth/dynamic-client-registration) + - Glean-defined, fine-grained scopes + - Recognized by issuer — **no extra header** + - Powers the [remote MCP server](/guides/mcp) + + + + **Your IdP issues the tokens** + + - Google, Okta, Azure Entra ID, OneLogin, etc. + - Token lifecycle owned by your IdP + - Requires the **`X-Glean-Auth-Type: OAUTH`** header + - Reuses your existing enterprise auth + + :::warning - OAuth authentication is **only supported for Client API**. Indexing API operations require Glean-issued tokens. +OAuth is supported for the **Client API only**. Indexing API operations require [Glean-issued tokens](/api-info/client/authentication/glean-issued) and do not accept OAuth. ::: --- ## Authentication Headers -OAuth requests require these specific headers: +Every OAuth request sends the access token as a bearer credential: ```bash Authorization: Bearer -X-Glean-Auth-Type: OAUTH ``` -### Header Details +Whether you **also** need `X-Glean-Auth-Type: OAUTH` depends on who issued the token: + +| Token source | `X-Glean-Auth-Type: OAUTH` | +|--------------|----------------------------| +| Glean OAuth Authorization Server (incl. Dynamic Client Registration) | Not required — Glean recognizes its own tokens by their issuer | +| External identity provider (Google, Okta, Azure, etc.) | **Required** — without it the token is treated as a Glean API token and rejected with `401` | -| Header | Description | Example Value | -|--------|-------------|---------------| -| `Authorization` | Bearer token from your OAuth provider | `Bearer eyJ0eXAiOiJKV1Q...` | -| `X-Glean-Auth-Type` | Required to specify OAuth authentication | `OAUTH` | +:::tip Using an SDK? +The official API clients accept an OAuth access token in their existing token field. See the OAuth section of the [TypeScript](/libraries/api-clients/typescript#oauth-access-tokens), [Python](/libraries/api-clients/python#oauth-access-tokens), [Go](/libraries/api-clients/go#oauth-access-tokens), or [Java](/libraries/api-clients/java#oauth-access-tokens) client docs. +::: --- -## Quick Setup Overview +## Setup + + + + +The [Glean OAuth Authorization Server](https://docs.glean.com/administration/oauth/authorization-server) is an OAuth 2.1 authorization server that issues access tokens for the Client API. It reuses your existing SSO for user authentication — it does not replace your IdP. - - Set up OAuth application in Google Workspace, Azure, Okta, or OneLogin + + The Glean OAuth Authorization Server is disabled by default. An administrator enables it in the Glean admin console. - - - Navigate to [Client API Settings](https://app.glean.com/admin/platform/tokenManagement?tab=client) and enable OAuth - - - - Add your OAuth application's Client ID to Glean's configuration + + + Register a **static client**, or allow [Dynamic Client Registration](https://docs.glean.com/administration/oauth/dynamic-client-registration) (primarily for MCP hosts). Administrators can restrict or disable dynamic registration. - - - Include `Authorization` and `X-Glean-Auth-Type` headers in your requests + + + Use the Authorization Code flow with **PKCE**. Discover endpoints from the server metadata document and exchange the authorization code for an access token. ---- +Endpoints (replace `` — see [finding your server URL](/get-started/authentication#finding-your-server-url)): -## Prerequisites +| Purpose | URL | +|---------|-----| +| OAuth server metadata | `https://-be.glean.com/.well-known/oauth-authorization-server` | +| Token | `https://-be.glean.com/oauth/token` | +| Dynamic Client Registration | `https://-be.glean.com/oauth/register` | -Before setting up OAuth authentication: +Tokens from the Glean Authorization Server are recognized by their issuer, so requests **do not** need the `X-Glean-Auth-Type` header. -- **Admin access** to Glean's admin console -- **Identity provider** account (Google Workspace, Azure, Okta, or OneLogin) -- **OAuth application** configured in your identity provider + + ---- +Register an OAuth 2.1 application in your enterprise IdP and configure Glean to accept its tokens. See [OAuth with IdP-issued tokens](https://docs.glean.com/administration/oauth/oauth-idp) for the authoritative guide. + + + + Set up an OAuth application in Google Workspace, Azure Entra ID, Okta, or OneLogin. Use the Authorization Code flow with **PKCE**; request the `offline_access` scope if you need a refresh token. + + + + In [Client API Settings](https://app.glean.com/admin/platform/tokenManagement?tab=client), enable **Allow OAuth token-based access**. + + + + Provide your OAuth application's Client ID and issuer so Glean can validate incoming tokens. + -## Provider-Specific Setup + + Include `Authorization: Bearer ` **and** `X-Glean-Auth-Type: OAUTH` on every Client API request. + + -Use the Help Center for detailed identity provider setup steps. +Provider setup references: - Google Workspace (OIDC): [Google (OIDC)](https://docs.glean.com/administration/identity/sso/configuration/google-oidc) - Azure Entra ID (OIDC): [Azure (OIDC)](https://docs.glean.com/administration/identity/sso/configuration/entra-id-oidc) - Okta (SAML): [Okta (SAML)](https://docs.glean.com/administration/identity/sso/configuration/okta-saml) - OneLogin: [Generic SAML guide](https://docs.glean.com/administration/identity/sso/configuration/generic-saml) + + + --- ## Implementation Examples -### Basic Search Request + + ```bash -curl -X POST https://instance-be.glean.com/rest/api/v1/search \ +curl -X POST https://-be.glean.com/rest/api/v1/search \ -H 'Authorization: Bearer ' \ - -H 'X-Glean-Auth-Type: OAUTH' \ -H 'Content-Type: application/json' \ -d '{ "query": "quarterly reports", @@ -100,91 +142,46 @@ curl -X POST https://instance-be.glean.com/rest/api/v1/search \ }' ``` -### Chat Request + + ```bash -curl -X POST https://instance-be.glean.com/rest/api/v1/chat \ +curl -X POST https://-be.glean.com/rest/api/v1/search \ -H 'Authorization: Bearer ' \ -H 'X-Glean-Auth-Type: OAUTH' \ -H 'Content-Type: application/json' \ -d '{ - "query": "What are the latest quarterly results?", - "conversationId": "optional-conversation-id" + "query": "quarterly reports", + "pageSize": 10 }' ``` ---- - -## Token Properties - -Understanding OAuth token characteristics: - -- **Scope**: Full Client API access (not restricted by scopes) -- **User context**: Treated as user-permissioned tokens -- **Expiration**: Managed by your identity provider -- **API Support**: Client API only (Indexing API not supported) -- **Security**: Leverages your existing identity provider security + + --- -## Testing OAuth Authentication - -### Test Command - -```bash -curl -X POST https://-be.glean.com/rest/api/v1/search \ - -H 'Authorization: Bearer ' \ - -H 'X-Glean-Auth-Type: OAUTH' \ - -H 'Content-Type: application/json' \ - -d '{"query": "test", "pageSize": 1}' -``` - -### Expected Response - -Successful authentication returns a 200 status with search results: +## Token Properties -```json -{ - "results": [...], - "trackingToken": "...", - "requestId": "..." -} -``` +- **Scope**: Full Client API access, unless your administrator restricts the integration to specific [scopes](/api-info/client/authentication/glean-issued) +- **User context**: Treated as user-permissioned; permissions are enforced by Glean at request time +- **Expiration & refresh**: Controlled by the issuer (Glean Authorization Server or your IdP). For a refresh token, request the `offline_access` scope and refresh with a standard OAuth library +- **API support**: Client API only (Indexing API not supported) --- ## Troubleshooting OAuth -### Common OAuth Errors - | Error | Cause | Solution | |-------|-------|----------| -| `Missing X-Glean-Auth-Type header` | OAuth header not set | Add `X-Glean-Auth-Type: OAUTH` header | -| `Invalid token format` | Malformed token | Verify token is valid JWT from your provider | -| `401 Unauthorized` | Invalid or expired token | Verify token is correct and not expired | -| `403 Forbidden` | OAuth not enabled | Contact admin to enable OAuth in Glean settings | +| `401 Unauthorized` / `Invalid Secret` | External-IdP token sent without `X-Glean-Auth-Type: OAUTH`, so it was treated as a Glean API token | Add the `X-Glean-Auth-Type: OAUTH` header (external-IdP tokens only) | +| `401 Unauthorized` | Invalid or expired token | Verify the token is valid and not expired; refresh if needed | +| `403 Forbidden` | OAuth not enabled, or client ID / issuer mismatch | Confirm OAuth is enabled in Glean and the registered client ID / issuer matches the token | +| `Invalid token format` | Malformed token | Verify the token is a valid JWT from your issuer | -### Debugging Steps - - - - Check that OAuth is enabled in [Glean Token Management](https://app.glean.com/admin/platform/tokenManagement?tab=client) - - - - Ensure you have both required headers: - - `Authorization: Bearer ` - - `X-Glean-Auth-Type: OAUTH` - - - - Verify your OAuth token is valid and not expired - - - - Start with a basic search request before testing complex operations - - +:::note +If you are using the Glean OAuth Authorization Server and still see a missing-header error, confirm the token was issued by Glean's server (not your IdP). Glean-issued tokens are detected by issuer and need no header; external-IdP tokens always do. +::: --- @@ -193,34 +190,32 @@ Successful authentication returns a 200 status with search results: ### Security - **Use HTTPS** for all OAuth flows and API requests -- **Validate tokens** before making API requests -- **Handle token refresh** gracefully in your application -- **Store tokens securely** - never commit to version control - -### Development - -- **Test OAuth flow** in development environment first -- **Handle errors gracefully** - OAuth tokens can expire or be revoked -- **Implement proper logging** for OAuth authentication events -- **Monitor token usage** through your identity provider +- **Use Authorization Code + PKCE** — it is required by OAuth 2.1 and by the Glean Authorization Server +- **Store tokens securely** — never commit them to version control +- **Handle token refresh** gracefully using a standard OAuth library ### Production -- **Use production OAuth applications** - don't use development credentials -- **Implement token caching** to reduce identity provider calls -- **Set up monitoring** for authentication failures -- **Plan for token rotation** and refresh scenarios +- **Use production OAuth applications** — don't ship development credentials +- **Cache tokens** to reduce calls to the issuer, and refresh before expiry +- **Monitor authentication failures** through your issuer and Glean --- ## Next Steps + + Pass an OAuth token to the TypeScript, Python, Go, or Java client + - Explore available Client API endpoints that work with OAuth + Explore Client API endpoints that work with OAuth + + + Admin guide to enabling and configuring the Glean Authorization Server - - Learn advanced search techniques with OAuth authentication + + OAuth-authenticated MCP access powered by the Glean Authorization Server diff --git a/docs/api-info/client/authentication/overview.mdx b/docs/api-info/client/authentication/overview.mdx index bd91a0648..027c6fe47 100644 --- a/docs/api-info/client/authentication/overview.mdx +++ b/docs/api-info/client/authentication/overview.mdx @@ -13,13 +13,13 @@ This guide helps you choose and implement the right authentication method for Gl - **Leverages your existing identity provider** - - - No token management required + **Glean Authorization Server or your IdP** + + - Tokens from the Glean OAuth Authorization Server (incl. DCR) or an external IdP - Works with Google, Azure, Okta, OneLogin - Full API access without scope restrictions - - Best for production applications - + - Powers the remote MCP server + [Setup OAuth →](./oauth) @@ -64,7 +64,7 @@ Different authentication methods require different headers: ```bash Authorization: Bearer -X-Glean-Auth-Type: OAUTH +X-Glean-Auth-Type: OAUTH # required for external-IdP tokens; omit for Glean Authorization Server tokens ``` @@ -171,7 +171,7 @@ Successful authentication returns a 200 status with search results: |-------|--------------|----------| | `401 Unauthorized` | Invalid or expired token | Verify token is correct and not expired | | `403 Forbidden` | Insufficient permissions | Check token scopes or OAuth settings | -| `Missing X-Glean-Auth-Type header` | OAuth header not set | Add `X-Glean-Auth-Type: OAUTH` for OAuth | +| `Missing X-Glean-Auth-Type header` / `Invalid Secret` | External-IdP OAuth token sent without the auth-type header | Add `X-Glean-Auth-Type: OAUTH` (external-IdP tokens only; not needed for Glean Authorization Server tokens) | | `Required header missing: X-Glean-ActAs` | Global token header missing | Add `X-Glean-ActAs: user@email.com` | For detailed troubleshooting, see your specific authentication guide. From 4ab2d071c0fda806e25f377f781e11f27d43ffc6 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Mon, 15 Jun 2026 13:36:24 -0700 Subject: [PATCH 3/6] docs(api-clients): add Authorization Code + PKCE examples per language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a complete, copy-paste OAuth example to each SDK doc using the conventional library for that ecosystem — openid-client (TypeScript), Authlib (Python), golang.org/x/oauth2 (Go), Spring Security (Java). Each runs the Authorization Code flow with PKCE, requests offline_access for a refresh token, then passes the access token to the Glean client. Library APIs verified against current versions; addresses prior review feedback (PKCE required, no insecure state handling, offline_access). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/libraries/api-clients/go.mdx | 73 ++++++++++++++++++ docs/libraries/api-clients/java.mdx | 92 +++++++++++++++++++++++ docs/libraries/api-clients/python.mdx | 55 ++++++++++++++ docs/libraries/api-clients/typescript.mdx | 70 +++++++++++++++++ 4 files changed, 290 insertions(+) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 60e080fa1..b5d092bbf 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -292,6 +292,79 @@ results, err := client.Client.Search.Query(ctx, &components.SearchRequest{ See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. +#### Complete Example: Authorization Code with PKCE + +This example uses [`golang.org/x/oauth2`](https://pkg.go.dev/golang.org/x/oauth2). It runs the Authorization Code flow with PKCE (`S256ChallengeOption` / `VerifierOption`) and `AccessTypeOffline` for a refresh token, then passes the access token to the Glean client. Read `AuthURL`/`TokenURL` from the issuer's metadata document. + +```go +package main + +import ( + "context" + "encoding/json" + "net/http" + "os" + + "golang.org/x/oauth2" + glean "github.com/gleanwork/api-client-go" + "github.com/gleanwork/api-client-go/models/components" + "github.com/gleanwork/api-client-go/models/operations" +) + +var ( + conf = &oauth2.Config{ + ClientID: os.Getenv("OAUTH_CLIENT_ID"), + ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), // omit for a public client + RedirectURL: "http://localhost:8080/callback", + Scopes: []string{"openid", "offline_access"}, // offline_access → refresh token + Endpoint: oauth2.Endpoint{ + AuthURL: os.Getenv("OAUTH_AUTH_URL"), + TokenURL: os.Getenv("OAUTH_TOKEN_URL"), + }, + } + // Demo only: generate and store the verifier per request (e.g. in session) in production. + verifier = oauth2.GenerateVerifier() +) + +func main() { + http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline, + oauth2.S256ChallengeOption(verifier)) + http.Redirect(w, r, url, http.StatusFound) + }) + + http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + tok, err := conf.Exchange(ctx, r.URL.Query().Get("code"), oauth2.VerifierOption(verifier)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + client := glean.New( + glean.WithAPIToken(tok.AccessToken), + glean.WithServerURL(os.Getenv("GLEAN_SERVER_URL")), + ) + + results, err := client.Client.Search.Query(ctx, &components.SearchRequest{ + Query: "quarterly reports", + }, // Omit this option when the token is from the Glean Authorization Server. + operations.WithSetHeaders(map[string]string{"X-Glean-Auth-Type": "OAUTH"})) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + json.NewEncoder(w).Encode(results) + }) + + http.ListenAndServe(":8080", nil) +} +``` + +:::tip +Access tokens expire. Wrap the token in `conf.TokenSource(ctx, tok)` to refresh automatically when you requested `offline_access`. +::: + ## Error Handling ```go diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index 4c190f085..5f5721aed 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -269,6 +269,98 @@ Glean client = Glean.builder() See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. +#### Complete Example: Authorization Code with PKCE + +This example uses Spring Security's OAuth2 Client. Point `OAUTH_ISSUER` at the Glean OAuth Authorization Server or your IdP. For a confidential client, enable PKCE with an authorization-request customizer (it is on by default for public clients). + +```yaml +# application.yml +spring: + security: + oauth2: + client: + registration: + glean: + provider: glean + client-id: ${OAUTH_CLIENT_ID} + client-secret: ${OAUTH_CLIENT_SECRET} + authorization-grant-type: authorization_code + scope: openid,offline_access # offline_access requests a refresh token + redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' + provider: + glean: + issuer-uri: ${OAUTH_ISSUER} + +glean: + server-url: ${GLEAN_SERVER_URL} +``` + +```java +// SecurityConfig.java — enable PKCE for a confidential client +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.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + @Bean + SecurityFilterChain filterChain(HttpSecurity http, ClientRegistrationRepository repo) + throws Exception { + var resolver = new DefaultOAuth2AuthorizationRequestResolver(repo, "/oauth2/authorization"); + resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); + http.oauth2Login(login -> login + .authorizationEndpoint(endpoint -> endpoint.authorizationRequestResolver(resolver))); + return http.build(); + } +} +``` + +```java +// SearchController.java — exchange the authorized client's token for a Glean call +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.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.glean.api_client.glean_api_client.Glean; +import com.glean.api_client.glean_api_client.models.components.*; +import java.util.List; + +@RestController +public class SearchController { + @Value("${glean.server-url}") + private String serverURL; + + @GetMapping("/search") + public List search( + @RegisteredOAuth2AuthorizedClient("glean") OAuth2AuthorizedClient authorizedClient) { + String accessToken = authorizedClient.getAccessToken().getTokenValue(); + + // For external-IdP tokens, add a custom HTTPClient that sets + // X-Glean-Auth-Type: OAUTH (see "OAuth Access Tokens" above). Glean + // Authorization Server tokens need no extra header. + Glean glean = Glean.builder() + .apiToken(accessToken) + .serverURL(serverURL) + .build(); + + return glean.client().search().search() + .searchRequest(SearchRequest.builder() + .query("quarterly reports") + .pageSize(10) + .build()) + .call() + .searchResponse() + .map(SearchResponse::results) + .orElse(List.of()); + } +} +``` + ## Error Handling ```java diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index 5762700d5..9544f7667 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -228,6 +228,61 @@ response = client.client.chat.create( See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. +#### Complete Example: Authorization Code with PKCE + +This example uses [Authlib](https://docs.authlib.org/) with Flask. Setting `code_challenge_method` enables PKCE; Authlib stores the verifier and state in the session and verifies them on the callback. Point `OAUTH_METADATA_URL` at the Glean OAuth Authorization Server metadata (`https://your-server-id-be.glean.com/.well-known/oauth-authorization-server`) or your IdP's discovery document. + +```python +import os +from flask import Flask, jsonify, url_for +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="glean", + client_id=os.environ["OAUTH_CLIENT_ID"], + client_secret=os.environ.get("OAUTH_CLIENT_SECRET"), # omit for a public client + server_metadata_url=os.environ["OAUTH_METADATA_URL"], + client_kwargs={ + "scope": "openid offline_access", # offline_access requests a refresh token + "code_challenge_method": "S256", # enable PKCE + }, +) + +@app.route("/login") +def login(): + return oauth.glean.authorize_redirect(url_for("callback", _external=True)) + +@app.route("/callback") +def callback(): + token = oauth.glean.authorize_access_token() # verifies state + PKCE, exchanges code + + with Glean( + api_token=token["access_token"], + server_url=os.environ["GLEAN_SERVER_URL"], + ) as glean: + results = glean.client.search.search( + query="quarterly reports", + page_size=10, + # Omit this header when the token is from the Glean Authorization Server. + http_headers={"X-Glean-Auth-Type": "OAUTH"}, + ) + titles = [r.title for r in (results.results or [])] + + return jsonify({"titles": titles}) + +if __name__ == "__main__": + app.run(port=5000) +``` + +:::tip +Access tokens expire. The `token` dict includes `refresh_token` when you request the `offline_access` scope; use it to obtain new access tokens before expiry. +::: + ## Error Handling ```python diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 7cbf375d1..518464a8d 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -217,6 +217,76 @@ const response = await client.client.chat.create({ See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. +#### Complete Example: Authorization Code with PKCE + +This example uses [`openid-client`](https://github.com/panva/openid-client) (v6) with Express. It runs the Authorization Code flow with PKCE, then passes the resulting access token to the Glean client. Point `OAUTH_ISSUER` at the Glean OAuth Authorization Server metadata (`https://your-server-id-be.glean.com/.well-known/oauth-authorization-server`) or your IdP's issuer. + +```typescript +import express from 'express'; +import session from 'express-session'; +import * as client from 'openid-client'; +import { Glean } from '@gleanwork/api-client'; + +const app = express(); +app.use(session({ secret: 'change-me', resave: false, saveUninitialized: false })); + +let config: client.Configuration; +async function init() { + config = await client.discovery( + new URL(process.env.OAUTH_ISSUER!), + process.env.OAUTH_CLIENT_ID!, + process.env.OAUTH_CLIENT_SECRET, // omit for a public client + ); +} + +app.get('/login', async (req, res) => { + // PKCE values must be generated per request and stored in the session. + const codeVerifier = client.randomPKCECodeVerifier(); + const codeChallenge = await client.calculatePKCECodeChallenge(codeVerifier); + (req.session as any).codeVerifier = codeVerifier; + + const params: Record = { + redirect_uri: 'http://localhost:3000/callback', + scope: 'openid offline_access', // offline_access requests a refresh token + code_challenge: codeChallenge, + code_challenge_method: 'S256', + }; + // state is only needed if the server doesn't advertise PKCE support + if (!config.serverMetadata().supportsPKCE()) { + const state = client.randomState(); + (req.session as any).state = state; + params.state = state; + } + res.redirect(client.buildAuthorizationUrl(config, params).href); +}); + +app.get('/callback', async (req, res) => { + const currentUrl = new URL(req.url, `http://${req.headers.host}`); + const tokens = await client.authorizationCodeGrant(config, currentUrl, { + pkceCodeVerifier: (req.session as any).codeVerifier, + expectedState: (req.session as any).state, // undefined when PKCE is used + }); + + const glean = new Glean({ + apiToken: tokens.access_token, + serverURL: process.env.GLEAN_SERVER_URL!, + }); + + const results = await glean.client.search.search( + { query: 'quarterly reports', pageSize: 10 }, + // Omit these headers when the token is from the Glean Authorization Server. + { headers: { 'X-Glean-Auth-Type': 'OAUTH' } }, + ); + res.json(results); +}); + +init().then(() => app.listen(3000)); +``` + +:::tip +Access tokens expire. Persist `tokens.refresh_token` and refresh with `client.refreshTokenGrant(config, refreshToken)` before expiry. +::: + ## Error Handling ```typescript From aa1a41f9248fa9ba1cb5cfc42eea19a0c13d834d Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Mon, 15 Jun 2026 14:39:24 -0700 Subject: [PATCH 4/6] fix(go-docs): add missing components import to ActAs example The Global Tokens with ActAs snippet imported only operations but used components.ChatRequest/ChatMessage/ChatMessageFragment. Caught by Graphite. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/libraries/api-clients/go.mdx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index b5d092bbf..9ab2af419 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -251,7 +251,10 @@ client := glean.New( Per-request headers are passed with `operations.WithSetHeaders`: ```go -import "github.com/gleanwork/api-client-go/models/operations" +import ( + "github.com/gleanwork/api-client-go/models/components" + "github.com/gleanwork/api-client-go/models/operations" +) response, err := client.Client.Chat.Create(ctx, components.ChatRequest{ Messages: []components.ChatMessage{{ From e1853b66ca09888c29be2ca0c61020e0d4b796f8 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Mon, 15 Jun 2026 15:03:20 -0700 Subject: [PATCH 5/6] docs(api-clients): drop "(Recommended)" from User-Scoped Tokens heading OAuth is the simpler, recommended path; user-scoped tokens are just one option, not the recommendation. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/libraries/api-clients/go.mdx | 2 +- docs/libraries/api-clients/java.mdx | 2 +- docs/libraries/api-clients/python.mdx | 2 +- docs/libraries/api-clients/typescript.mdx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 9ab2af419..9106c8b06 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -237,7 +237,7 @@ func batchSearch(client *glean.Client, queries []string) ([]glean.SearchResponse ## Authentication -### User-Scoped Tokens (Recommended) +### User-Scoped Tokens ```go client := glean.New( diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index 5f5721aed..30be8b567 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -210,7 +210,7 @@ public class GleanController { ## Authentication -### User-Scoped Tokens (Recommended) +### User-Scoped Tokens ```java Glean client = Glean.builder() diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index 9544f7667..e38575f43 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -188,7 +188,7 @@ if user_input: ## Authentication -### User-Scoped Tokens (Recommended) +### User-Scoped Tokens ```python client = Glean( diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 518464a8d..27fa534f1 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -175,7 +175,7 @@ app.post('/api/chat', async (req, res) => { ## Authentication -### User-Scoped Tokens (Recommended) +### User-Scoped Tokens ```typescript const client = new Glean({ From 37a09aaf1b35a05e8a14b047975d5448742bdf49 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 17 Jun 2026 11:04:16 -0700 Subject: [PATCH 6/6] docs: address review feedback on OAuth examples + auth guide David's comments: - oauth.mdx: note the server metadata document is the authoritative source for endpoints - oauth.mdx: clarify scopes (static clients select scopes at client creation; DCR clients limited to a restricted subset) - oauth.mdx: reword token "caching" as reuse-until-expiry - overview.mdx: header is "optional" (not "omit") for Authorization Server tokens - SDK flow examples (go/python/typescript/java): add the SEARCH scope so the token can call /search Also make the touched examples actually compile (the "does it work?" concern): - Go: WithAPIToken -> WithSecurity; ChatMessageFragment.Text needs *string (glean.String) - TS: search.search -> search.query; per-request options is the last positional arg - Python: search.search -> search.query - Java: search() -> query(); searchRequest() -> request(); map -> flatMap Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/api-info/client/authentication/oauth.mdx | 6 ++++-- docs/api-info/client/authentication/overview.mdx | 2 +- docs/libraries/api-clients/go.mdx | 11 ++++++----- docs/libraries/api-clients/java.mdx | 8 ++++---- docs/libraries/api-clients/python.mdx | 4 ++-- docs/libraries/api-clients/typescript.mdx | 16 +++++++++------- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/docs/api-info/client/authentication/oauth.mdx b/docs/api-info/client/authentication/oauth.mdx index 2ac73cf8f..76d7fa96f 100644 --- a/docs/api-info/client/authentication/oauth.mdx +++ b/docs/api-info/client/authentication/oauth.mdx @@ -90,6 +90,8 @@ Endpoints (replace `` — see [finding your server URL](/get-started/a | Token | `https://-be.glean.com/oauth/token` | | Dynamic Client Registration | `https://-be.glean.com/oauth/register` | +The metadata document is the authoritative source for endpoints — fetch it to discover the current authorization, token, registration, and any other endpoints rather than relying on the values listed above. + Tokens from the Glean Authorization Server are recognized by their issuer, so requests **do not** need the `X-Glean-Auth-Type` header. @@ -163,7 +165,7 @@ curl -X POST https://-be.glean.com/rest/api/v1/search \ ## Token Properties -- **Scope**: Full Client API access, unless your administrator restricts the integration to specific [scopes](/api-info/client/authentication/glean-issued) +- **Scope**: Governed by Glean [scopes](/api-info/client/authentication/glean-issued) (for example `SEARCH`, `CHAT`). For static clients, the allowed scopes are selected when the client is created or edited; dynamically registered (DCR) clients are limited to a restricted subset. Request only the scopes your integration needs - **User context**: Treated as user-permissioned; permissions are enforced by Glean at request time - **Expiration & refresh**: Controlled by the issuer (Glean Authorization Server or your IdP). For a refresh token, request the `offline_access` scope and refresh with a standard OAuth library - **API support**: Client API only (Indexing API not supported) @@ -197,7 +199,7 @@ If you are using the Glean OAuth Authorization Server and still see a missing-he ### Production - **Use production OAuth applications** — don't ship development credentials -- **Cache tokens** to reduce calls to the issuer, and refresh before expiry +- **Reuse an access token until it expires** rather than requesting a new one per call, and refresh once it expires - **Monitor authentication failures** through your issuer and Glean --- diff --git a/docs/api-info/client/authentication/overview.mdx b/docs/api-info/client/authentication/overview.mdx index 027c6fe47..a6d08cc0e 100644 --- a/docs/api-info/client/authentication/overview.mdx +++ b/docs/api-info/client/authentication/overview.mdx @@ -64,7 +64,7 @@ Different authentication methods require different headers: ```bash Authorization: Bearer -X-Glean-Auth-Type: OAUTH # required for external-IdP tokens; omit for Glean Authorization Server tokens +X-Glean-Auth-Type: OAUTH # required for external-IdP tokens; optional for Glean Authorization Server tokens ``` diff --git a/docs/libraries/api-clients/go.mdx b/docs/libraries/api-clients/go.mdx index 9106c8b06..a0e5ffc48 100644 --- a/docs/libraries/api-clients/go.mdx +++ b/docs/libraries/api-clients/go.mdx @@ -252,6 +252,7 @@ Per-request headers are passed with `operations.WithSetHeaders`: ```go import ( + glean "github.com/gleanwork/api-client-go" "github.com/gleanwork/api-client-go/models/components" "github.com/gleanwork/api-client-go/models/operations" ) @@ -259,7 +260,7 @@ import ( response, err := client.Client.Chat.Create(ctx, components.ChatRequest{ Messages: []components.ChatMessage{{ Fragments: []components.ChatMessageFragment{{ - Text: "Hello", + Text: glean.String("Hello"), }}, }}, }, nil, operations.WithSetHeaders(map[string]string{ @@ -269,11 +270,11 @@ response, err := client.Client.Chat.Create(ctx, components.ChatRequest{ ### OAuth Access Tokens -An OAuth access token is a bearer credential, so it goes in the same `WithAPIToken` option: +An OAuth access token is a bearer credential, so it goes in the same `WithSecurity` option: ```go client := glean.New( - glean.WithAPIToken(oauthAccessToken), + glean.WithSecurity(oauthAccessToken), glean.WithServerURL("https://your-server-id-be.glean.com"), ) ``` @@ -319,7 +320,7 @@ var ( ClientID: os.Getenv("OAUTH_CLIENT_ID"), ClientSecret: os.Getenv("OAUTH_CLIENT_SECRET"), // omit for a public client RedirectURL: "http://localhost:8080/callback", - Scopes: []string{"openid", "offline_access"}, // offline_access → refresh token + Scopes: []string{"openid", "offline_access", "SEARCH"}, // SEARCH lets the token call /search; offline_access → refresh token Endpoint: oauth2.Endpoint{ AuthURL: os.Getenv("OAUTH_AUTH_URL"), TokenURL: os.Getenv("OAUTH_TOKEN_URL"), @@ -345,7 +346,7 @@ func main() { } client := glean.New( - glean.WithAPIToken(tok.AccessToken), + glean.WithSecurity(tok.AccessToken), glean.WithServerURL(os.Getenv("GLEAN_SERVER_URL")), ) diff --git a/docs/libraries/api-clients/java.mdx b/docs/libraries/api-clients/java.mdx index 30be8b567..463acc684 100644 --- a/docs/libraries/api-clients/java.mdx +++ b/docs/libraries/api-clients/java.mdx @@ -285,7 +285,7 @@ spring: client-id: ${OAUTH_CLIENT_ID} client-secret: ${OAUTH_CLIENT_SECRET} authorization-grant-type: authorization_code - scope: openid,offline_access # offline_access requests a refresh token + scope: openid,offline_access,SEARCH # SEARCH lets the token call /search; offline_access requests a refresh token redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' provider: glean: @@ -348,14 +348,14 @@ public class SearchController { .serverURL(serverURL) .build(); - return glean.client().search().search() - .searchRequest(SearchRequest.builder() + return glean.client().search().query() + .request(SearchRequest.builder() .query("quarterly reports") .pageSize(10) .build()) .call() .searchResponse() - .map(SearchResponse::results) + .flatMap(SearchResponse::results) .orElse(List.of()); } } diff --git a/docs/libraries/api-clients/python.mdx b/docs/libraries/api-clients/python.mdx index e38575f43..5fad84234 100644 --- a/docs/libraries/api-clients/python.mdx +++ b/docs/libraries/api-clients/python.mdx @@ -248,7 +248,7 @@ oauth.register( client_secret=os.environ.get("OAUTH_CLIENT_SECRET"), # omit for a public client server_metadata_url=os.environ["OAUTH_METADATA_URL"], client_kwargs={ - "scope": "openid offline_access", # offline_access requests a refresh token + "scope": "openid offline_access SEARCH", # SEARCH lets the token call /search (a Glean scope); offline_access requests a refresh token "code_challenge_method": "S256", # enable PKCE }, ) @@ -265,7 +265,7 @@ def callback(): api_token=token["access_token"], server_url=os.environ["GLEAN_SERVER_URL"], ) as glean: - results = glean.client.search.search( + results = glean.client.search.query( query="quarterly reports", page_size=10, # Omit this header when the token is from the Glean Authorization Server. diff --git a/docs/libraries/api-clients/typescript.mdx b/docs/libraries/api-clients/typescript.mdx index 27fa534f1..59de4d024 100644 --- a/docs/libraries/api-clients/typescript.mdx +++ b/docs/libraries/api-clients/typescript.mdx @@ -208,11 +208,12 @@ const client = new Glean({ Tokens issued by the **Glean OAuth Authorization Server** (including tokens obtained via Dynamic Client Registration) are detected automatically. Tokens issued by an **external identity provider** (Google, Okta, Azure, etc.) additionally require the `X-Glean-Auth-Type: OAUTH` header on each request: ```typescript -const response = await client.client.chat.create({ - messages: [{ fragments: [{ text: "Hello" }] }] -}, { - headers: { "X-Glean-Auth-Type": "OAUTH" } -}); +const response = await client.client.chat.create( + { messages: [{ fragments: [{ text: "Hello" }] }] }, + undefined, + undefined, + { headers: { "X-Glean-Auth-Type": "OAUTH" } }, +); ``` See the [OAuth authentication guide](/api-info/client/authentication/oauth) for identity-provider setup. @@ -247,7 +248,7 @@ app.get('/login', async (req, res) => { const params: Record = { redirect_uri: 'http://localhost:3000/callback', - scope: 'openid offline_access', // offline_access requests a refresh token + scope: 'openid offline_access SEARCH', // SEARCH lets the token call /search (a Glean scope); offline_access requests a refresh token code_challenge: codeChallenge, code_challenge_method: 'S256', }; @@ -272,8 +273,9 @@ app.get('/callback', async (req, res) => { serverURL: process.env.GLEAN_SERVER_URL!, }); - const results = await glean.client.search.search( + const results = await glean.client.search.query( { query: 'quarterly reports', pageSize: 10 }, + undefined, // Omit these headers when the token is from the Glean Authorization Server. { headers: { 'X-Glean-Auth-Type': 'OAUTH' } }, );