Skip to main content

Get tokens with a static OAuth client

You can obtain access and refresh tokens from the Glean OAuth Authorization Server by using a static OAuth client.

These examples use the authorization code flow with Proof Key for Code Exchange (PKCE), a localhost redirect URI, and client_secret_post so you can inspect each step of the exchange.

When you call the Glean Client API with an OAuth access token, include the header Authorization: Bearer <token>.

info

If your static client uses a different token endpoint authentication method, adjust the token and refresh requests to match your client configuration.

Prerequisites​

Complete the following prerequisites:

In the examples below, copy your full backend URL from app.glean.com/admin/about-glean under Server instance (QE), and replace https://tenant_id-be.glean.com with it.

Examples​

See the following examples for Bash and TypeScript to learn how to get tokens with a static OAuth client.

Important

These examples are not production code. They only show how to complete the OAuth authorization code flow and make a basic client API request. In production, follow best practices for credential storage, token management, redirect handling, and error handling.

Bash prerequisites​

Install the following tools before you run the Bash example:

  • bash
  • curl
  • jq
  • openssl
  • nc

1. Set up variables​

Register the redirect URI http://127.0.0.1:9090/oauth/callback:

export CLIENT_ID='YOUR_STATIC_CLIENT_ID'
export CLIENT_SECRET='YOUR_STATIC_CLIENT_SECRET'
export GLEAN_BACKEND_URL='https://tenant_id-be.glean.com'

export REDIRECT_URI='http://127.0.0.1:9090/oauth/callback'
export CALLBACK_CAPTURE='/tmp/glean-oauth-callback.request'

2. Discover OAuth metadata​

Discover the OAuth metadata for the Glean OAuth Authorization Server:

export OAUTH_METADATA=$(curl -fsS "$GLEAN_BACKEND_URL/.well-known/oauth-authorization-server")

export AUTHORIZATION_ENDPOINT=$(jq -r '.authorization_endpoint' <<<"$OAUTH_METADATA")
export TOKEN_ENDPOINT=$(jq -r '.token_endpoint' <<<"$OAUTH_METADATA")
export ISSUER=$(jq -r '.issuer' <<<"$OAUTH_METADATA")

echo "issuer=$ISSUER"
echo "authorization_endpoint=$AUTHORIZATION_ENDPOINT"
echo "token_endpoint=$TOKEN_ENDPOINT"

echo "code challenge methods:"
jq -r '.code_challenge_methods_supported[]?' <<<"$OAUTH_METADATA"

echo "token endpoint auth methods:"
jq -r '.token_endpoint_auth_methods_supported[]?' <<<"$OAUTH_METADATA"

3. Generate PKCE values​

Generate the PKCE values:

b64url() {
openssl base64 -A | tr '+/' '-_' | tr -d '='
}

export STATE=$(openssl rand -base64 32 | tr -d '\n=' | tr '+/' '-_' | cut -c1-43)
export CODE_VERIFIER=$(openssl rand -base64 64 | tr -d '\n=' | tr '+/' '-_' | cut -c1-86)
export CODE_CHALLENGE=$(
printf '%s' "$CODE_VERIFIER" | openssl dgst -binary -sha256 | b64url
)

4. Start a localhost callback listener​

Before you open the authorization URL, create a response file for the callback:

cat > /tmp/glean-oauth-callback.response <<'EOF'
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Connection: close

OAuth callback captured. You can return to the terminal.
EOF

rm -f "$CALLBACK_CAPTURE"
nc -l 127.0.0.1 9090 < /tmp/glean-oauth-callback.response > "$CALLBACK_CAPTURE"

If you do not want to use nc, you can use any local listener that accepts one request on 127.0.0.1:9090 and writes the raw HTTP request to "$CALLBACK_CAPTURE". For example, you can use python -m http.server 9090 to start a simple HTTP server.

5. Open the authorization URL​

Open the authorization URL:

export AUTHORIZE_URL=$(
jq -rn \
--arg endpoint "$AUTHORIZATION_ENDPOINT" \
--arg client_id "$CLIENT_ID" \
--arg redirect_uri "$REDIRECT_URI" \
--arg state "$STATE" \
--arg code_challenge "$CODE_CHALLENGE" \
'$endpoint + "?" + (
[
["response_type", "code"],
["client_id", $client_id],
["redirect_uri", $redirect_uri],
["state", $state],
["code_challenge", $code_challenge],
["code_challenge_method", "S256"]
]
| map("\(.[0])=\(.[1] | @uri)")
| join("&")
)'
)

printf '%s\n' "$AUTHORIZE_URL"

This code opens the URL in your browser. On macOS, you can also run open $AUTHORIZE_URL.

Sign in and complete the consent flow in your browser. Glean should redirect back to http://127.0.0.1:9090/oauth/callback?..., and the nc process should exit after writing the request to "$CALLBACK_CAPTURE".

6. Extract the authorization code​

Extract the authorization code:

# Extract the authorization code from the callback request
urldecode() {
local data="${1//+/ }"
printf '%b' "${data//%/\\x}"
}

export REQUEST_LINE=$(sed -n '1s/\r$//p' "$CALLBACK_CAPTURE")
export QUERY_STRING="${REQUEST_LINE#GET /oauth/callback?}"
export QUERY_STRING="${QUERY_STRING%% HTTP/*}"

export RAW_CODE=$(printf '%s\n' "$QUERY_STRING" | tr '&' '\n' | sed -n 's/^code=//p')
export RAW_STATE=$(printf '%s\n' "$QUERY_STRING" | tr '&' '\n' | sed -n 's/^state=//p')
export RAW_ERROR=$(printf '%s\n' "$QUERY_STRING" | tr '&' '\n' | sed -n 's/^error=//p')

export AUTH_CODE=$(urldecode "$RAW_CODE")
export RETURNED_STATE=$(urldecode "$RAW_STATE")
export RETURNED_ERROR=$(urldecode "$RAW_ERROR")

if [ -n "$RETURNED_ERROR" ]; then
echo "OAuth error: $RETURNED_ERROR" >&2
exit 1
fi

if [ "$RETURNED_STATE" != "$STATE" ]; then
echo "state mismatch" >&2
exit 1
fi

printf 'Captured auth code prefix: %s\n' "${AUTH_CODE:0:16}"

7. Exchange the code for tokens​

Exchange the authorization code for tokens using client_secret_post:

export TOKEN_RESPONSE=$(
curl -fsS -X POST "$TOKEN_ENDPOINT" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode "code=$AUTH_CODE" \
--data-urlencode "redirect_uri=$REDIRECT_URI" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET" \
--data-urlencode "code_verifier=$CODE_VERIFIER"
)

jq . <<<"$TOKEN_RESPONSE"

export ACCESS_TOKEN=$(jq -r '.access_token' <<<"$TOKEN_RESPONSE")
export REFRESH_TOKEN=$(jq -r '.refresh_token // empty' <<<"$TOKEN_RESPONSE")

8. Call the Chat API​

Use the access token to make a simple client API request:

export CHAT_RESPONSE=$(
curl -fsS -X POST "$GLEAN_BACKEND_URL/rest/api/v1/chat" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"messages": [{"fragments": [{"text": "Who is on the executive team?"}]}]
}'
)

jq . <<<"$CHAT_RESPONSE"

9. Inspect the access token​

Decode the JWT locally so you can inspect the claims:

jwt_b64url_decode() {
local input="$1"
case $((${#input} % 4)) in
2) input="${input}==" ;;
3) input="${input}=" ;;
1)
echo "invalid base64url input" >&2
return 1
;;
esac
printf '%s' "$input" | tr '_-' '/+' | openssl base64 -d -A
}

export JWT_HEADER_B64=$(cut -d '.' -f1 <<<"$ACCESS_TOKEN")
export JWT_PAYLOAD_B64=$(cut -d '.' -f2 <<<"$ACCESS_TOKEN")

echo "jwt header:"
jwt_b64url_decode "$JWT_HEADER_B64" | jq .

echo "jwt claims:"
jwt_b64url_decode "$JWT_PAYLOAD_B64" | jq .
note

This does not verify the signature.

The decoded header includes a kid (key ID). To verify the signature, fetch the matching key from the Glean JWKS (the key whose kid equals the token's) and validate the token against it. See Verify tokens against the JWKS.

10. Use the refresh token​

Use the refresh token to obtain a new access token:

if [ -z "$REFRESH_TOKEN" ]; then
echo "No refresh token was returned"
exit 1
fi

export REFRESH_RESPONSE=$(
curl -fsS -X POST "$TOKEN_ENDPOINT" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=refresh_token' \
--data-urlencode "refresh_token=$REFRESH_TOKEN" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "client_secret=$CLIENT_SECRET"
)

jq . <<<"$REFRESH_RESPONSE"

export REFRESHED_ACCESS_TOKEN=$(jq -r '.access_token' <<<"$REFRESH_RESPONSE")
export REFRESHED_REFRESH_TOKEN=$(jq -r '.refresh_token // empty' <<<"$REFRESH_RESPONSE")

After you obtain an access token, send it as a bearer token when you call the Glean Client API. Include the header Authorization: Bearer <token>.

See also​