FLOWLOGIC
module /api-auth-patterns

unit U11 of 4

OAuth2 in practice

auth-code + PKCE, refresh rotation

OAuth2 is how you act on a user's behalf without ever holding their password — the client's Salesforce, Google, or Xero grants your flow a scoped access token instead. For any interactive integration the authorization-code flow is the one you want: the user consents in a browser, the provider hands back a short-lived code, and you swap that code for tokens server-side. Access tokens expire in minutes to an hour by design, so the real work in production is not getting the first token — it is refreshing it forever without a human in the loop.

The engine wires most of this for you: create an OAuth2 Connection, set the auth and token URLs, scopes, and client id/secret, and the built-in flow stores and refreshes the token for you. PKCE hardens the code exchange — you send a code_challenge up front and prove ownership with the code_verifier at swap time, so a stolen code is useless on its own. When you drive the flow by hand in an HTTP Request Piece, you own the token store: persist the refresh token, watch expires_in, and swap early rather than on the 401.

Where it breaks: refresh-token rotation. Many providers issue a brand-new refresh token on every refresh and silently revoke the one you just used; keep reusing the original and the provider reads the replay as a stolen credential, then kills the entire grant. Always persist the new refresh_token from each response, and never let two refreshes race on the same credential at once.

worked example

A client’s flow trading an authorization code for tokens against their Xero tenant, PKCE verifier included.

curl -s -X POST https://identity.acme-books.dev/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code=ac_9f2b1e7c \
  -d code_verifier=x7Q…redacted…kP0 \
  -d redirect_uri=https://hooks.youragency.dev/oauth/callback \
  -d client_id=cli_4821 \
  -d client_secret=sk-•••

# 200 → { access_token, expires_in: 1800, refresh_token: rt_NEW }
# store rt_NEW, discard the old refresh token, refresh at ~T-120s

field checklist

common failure — Reusing a rotated refresh token

A nightly sync ran fine for weeks, then every run started returning invalid_grant and the integration went dark. The provider rotated refresh tokens on each use, but the flow always replayed the original — read as a stolen credential, so the grant was revoked. Capture refresh_token from each token response, overwrite the stored value, and serialise refreshes so two runs never race.

check your understanding

A nightly sync ran for weeks, then every run began failing and the grant was revoked entirely. The provider rotates credentials on each refresh. Which field must be persisted from every token response?

store the newreturned by each refresh, and discard the old one

next unit opens once this is passed

sandbox validation

The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.