Every API team hits the same wall. The endpoints work in isolation, then someone turns on OAuth 2.0 and half the test suite starts returning 401s. Suddenly you’re juggling authorization servers, short-lived access tokens, and scopes, and copying tokens by hand from a curl response into a header field gets old by the third run.
The fix isn’t skipping auth in your tests. It’s making token handling part of the test setup so it stops being manual work. This guide covers the two flows you’ll meet in almost every test plan: the OAuth authorization code flow (with PKCE) for APIs acting on behalf of a user, and the client credentials flow for machine-to-machine calls. If you want the full map of grants first, our OAuth 2.0 flows overview walks through all of them.
Then we get hands-on: configuring OAuth 2.0 auth in Apidog, fetching a token once and reusing it across requests, letting expired tokens refresh on their own, inheriting auth at the folder level, and testing the failure paths your security review will ask about.
The two flows that matter for API testing
OAuth 2.0 defines several grant types, but for day-to-day API testing you’ll spend most of your time with two of them. Pick based on one question: does the API act on behalf of a user, or on behalf of a service?
Authorization code flow, with PKCE
The authorization code flow is the standard way to get a token tied to a user. The client sends the user to the authorization server, the user logs in and consents, the server redirects back with a one-time code, and the client swaps the code for an access token at the token endpoint. RFC 6749 defines the whole dance in section 4.1.
PKCE (Proof Key for Code Exchange, RFC 7636) hardens the exchange. The client generates a random verifier, sends a hashed challenge with the authorization request, then proves it holds the original verifier when redeeming the code. An attacker who intercepts the code can’t use it. PKCE started as a mobile-app fix, but current guidance from oauth.net recommends it for every authorization code exchange, confidential clients included.
Test with this flow whenever the endpoint’s behavior depends on who the user is: GET /orders returning only the caller’s orders, role-gated admin endpoints, per-user rate limits.
Client credentials flow
The OAuth 2.0 client credentials grant skips the user entirely. The client authenticates with its own ID and secret and receives a token representing the application itself. One POST to the token endpoint, no browser, no redirect:
curl -X POST https://auth.example.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=orders_service \
-d client_secret=s3cr3t_value \
-d scope="orders:read orders:write"
This is the flow for machine-to-machine APIs: internal microservices, cron jobs, CI pipelines calling a deployment API. It’s also the workhorse of automated testing, because it needs no human in the loop. If your test environment lets you provision a test client, use client credentials for everything except the cases where user identity is the thing under test.
Configuring OAuth 2.0 auth in Apidog
Apidog treats OAuth 2.0 as a first-class auth type. You configure it once, in the Auth tab of a request or folder, and the platform handles fetching, attaching, and refreshing tokens. Supported grant types include Authorization Code, Authorization Code (With PKCE), Client Credentials, Password Credentials, and Implicit.
Here’s the setup for the two flows above, using a fictional order-management API.
Client credentials setup
Open the request (or better, the folder; more on this below), switch the auth type to OAuth 2.0, and pick Client Credentials as the grant type. Fill in:
- Access Token URL:
https://auth.example.com/oauth/token - Client ID:
orders_service - Client Secret: your provisioned secret
- Scope:
orders:read orders:write(set under the advanced options)
Apidog gives you two ways to deliver the credentials: as a Basic Auth header or in the request body. Match whatever your authorization server expects; Auth0 and Okta accept both, but some in-house servers only parse the body.
Click Get Token. Apidog calls the token endpoint, stores the result, and shows the token along with its validity period. From then on, every send attaches it to the Authorization header with the Bearer prefix. No copy-paste, no {{token}} variable plumbing.
Authorization code setup with PKCE
For user-context testing, choose Authorization Code (With PKCE) as the grant type. PKCE is its own grant option in Apidog, not a checkbox. You’ll need a few more fields:
- Auth URL:
https://auth.example.com/oauth/authorize - Access Token URL:
https://auth.example.com/oauth/token - Callback URL: the redirect URI registered with your provider
- Client ID and Client Secret: from your OAuth app registration
Click Get Token and Apidog opens a browser window pointing at the login page. Sign in as your test user, approve the consent screen, and the token comes back and lands in the same managed slot as before. If your provider returns an OpenID Connect ID token alongside the access token, a Token Type Used option lets you switch which one gets attached; useful when the API under test validates ID tokens.
One practical tip: keep a dedicated test user per role you need to cover (buyer, admin, read-only auditor). Fetching a token as each user and re-running the same scenario is the fastest way to verify role-based access rules.
Token reuse and auto-refresh
Access tokens expire, usually within an hour. Before Apidog handled this, an expired token meant a failed run and a manual re-fetch, which is exactly the kind of flaky failure teams learn to ignore.
Now Apidog refreshes OAuth 2.0 tokens on its own when the authorization server issued a refresh token, a capability that shipped in the June update. When the stored access token expires, Apidog uses the refresh token to get a new one and swaps it in before sending. You can also point it at a custom refresh token URL in the advanced settings if your provider separates the two endpoints.
For client credentials, many servers skip refresh tokens entirely (the spec allows it, since the client can re-authenticate any time). In practice this doesn’t hurt: re-fetching with Get Token is one click, and scheduled or CI runs can request a fresh token at the start of each run.
Inherit auth at the folder level
Configuring OAuth on every request is the wrong altitude. Apidog lets you set auth on a folder, and requests inside it inherit the configuration from their parent. Set OAuth 2.0 once on your “Orders API” folder and every request under it, including new ones your teammates add next sprint, sends the same managed token.
This matters most in multi-step test scenarios. A checkout scenario might chain POST /carts, POST /carts/{id}/items, and POST /orders. With folder-level auth, all three steps share one token and one configuration. When the token expires mid-scenario, the auto-refresh covers it. And when your security team rotates the client secret, you update one folder instead of forty requests.
Requests keep the option to override the parent, which is exactly what you want for negative tests. More on those now.
Testing the failure paths
Happy-path OAuth tests prove your token pipeline works. Failure-path tests prove your API enforces auth. Skip them and you’re trusting the framework defaults. Here are the three cases worth automating; for a refresher on what each status code should mean, see our comparison of API keys and bearer tokens.
Expired or missing token: expect 401
Duplicate one request in your scenario and override its inherited auth with either no auth or a hardcoded, long-dead bearer token such as Bearer expired_token_do_not_rotate. Assert on:
- Status code equals
401 - The
WWW-Authenticateresponse header is present (RFC 6749’s companion, RFC 6750, expects it) - The body doesn’t leak stack traces or internal hostnames
A 200 here is a critical bug. A 403 is a design smell worth a ticket: the server should distinguish “I don’t know who you are” from “I know you, and no.”
Wrong scope: expect 403
Provision a second test client limited to orders:read, fetch its token, and call a write endpoint like POST /orders. Assert the status is 403 and, if your API follows RFC 6750, the WWW-Authenticate header includes error="insufficient_scope". This test catches the classic misconfiguration where scopes are checked at the gateway for some routes and forgotten on others. If scopes are new to your team, OAuth 2.0 scopes explained covers how to slice them.
Invalid client: expect a clean token-endpoint error
Point a request straight at https://auth.example.com/oauth/token with a bogus client_secret. Per RFC 6749 section 5.2, the server should return 400 (or 401 for failed client authentication) with a JSON body containing "error": "invalid_client". Assert on both. Authorization servers are APIs too, and their error contract is part of your surface.
Asserting on token responses in test scenarios
The token endpoint deserves its own coverage beyond the invalid-client case. Add a step in your test scenario calling the token endpoint directly, then attach assertions on the response:
access_tokenexists and is non-emptytoken_typeequalsbearer(case-insensitive per spec)expires_inis greater than 0 and within your policy, say no more than 3600scopematches what was requested, catching servers silently narrowing grants
Apidog’s test scenarios let you add these as visual assertions on the response JSON, with no scripting required, and you can extract access_token into a variable for a follow-up step when you want to test the raw handshake instead of using the managed auth. Wire the scenario into your CI run and a misbehaving authorization server fails the build instead of surfacing as a mystery 401 in production.
The full loop looks like this: folder-level OAuth 2.0 config for the happy path, per-request overrides for the 401 and 403 cases, and one scenario hammering the token endpoint’s contract. That covers user-context APIs through authorization code with PKCE and service-to-service APIs through client credentials, with token refresh handled for you. Download Apidog and try it free; the OAuth 2.0 auth type works on the free plan, so you can point it at your own token endpoint in a few minutes.
FAQ
Which OAuth flow should I use for API testing?
Use client credentials for anything machine-to-machine and for most automated suites, since it needs no browser interaction. Use the authorization code flow with PKCE when the test depends on user identity: per-user data isolation, role checks, or consent behavior. Avoid the implicit and password grants in new test plans; both are discouraged in current OAuth guidance.
How do I refresh an expired token automatically in Apidog?
Configure OAuth 2.0 in the Auth tab and fetch a token with Get Token. When the authorization server returns a refresh token, Apidog refreshes the access token on expiry without you re-authenticating, and you can set a separate refresh token URL in the advanced settings if your provider uses one. For client credentials setups without refresh tokens, re-running Get Token issues a fresh one.
Can every request in a scenario share one OAuth token?
Yes. Set the OAuth 2.0 configuration on the parent folder and the requests inside inherit it, so a multi-step scenario runs under a single managed token. Individual requests can still override the folder config, which is how you slot negative tests (expired token, wrong scope) into the same scenario.
What should a 401 versus a 403 mean in OAuth-protected APIs?
Return 401 when authentication failed: the token is missing, expired, or malformed. Return 403 when the token is valid but lacks permission, such as a missing scope. Mixing them up breaks client retry logic, because a 401 tells the client to re-authenticate while a 403 tells it to stop. Our guide to testing JWT authentication digs into validating the token itself.



