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 setup steps:

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.

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://your-instance-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.

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