Teams choose between a static API key and an OAuth access token mostly by habit, and the choice then fixes how long a stolen credential works, what it can reach and whether anyone can tell who used it. This guide is a decision guide: it compares the two models property by property, sets out when each is the right answer, covers the hardened middle ground (signed requests, sender-constrained tokens, keys exchanged for tokens) and gives a migration path from keys to tokens. It is for API owners deciding what to accept, platform engineers deciding what workloads should present, and security architects writing the standard. Lifecycle detail lives elsewhere: see the API Key Management Guide, the Machine-to-Machine OAuth (Client Credentials) Guide and the OAuth 2.0 and OpenID Connect Guide.
Key takeaways
- Both are usually bearer credentials. RFC 6750 defines a bearer token as one any holder can use without proving possession of a key. A plain API key behaves the same way, so the real difference is lifetime, scope and issuance, not the header.
- Lifetime is the biggest gap. An API key typically works until someone revokes it; an OAuth access token expires on its own, and RFC 6750 recommends lifetimes of one hour or less.
- An API key often identifies a project, not a caller. Google Cloud states that a standard API key does not authenticate a principal, and OWASP API2:2023 says API keys are not user authentication.
- Tokens move the long-lived secret, they do not remove it. Client credentials still need a client secret, private key or certificate. The win comes when that secret is asymmetric, federated or platform-issued.
- Keys are still right for some jobs. Low-risk, quota-style or public client identification and simple partner integrations can stay on keys if they are restricted, monitored and rotatable.
What each credential actually is
An API key is a string issued by the API provider and stored by the provider (ideally as a hash) against an account, project or application. The caller sends it on every request, usually in a header. There is no standard: format, scoping, expiry and revocation are whatever the provider built. Some "keys" are really identifiers. Google Cloud says standard API keys associate requests with a project for billing and quota, and IAM cannot check the caller's authorisation with them.
An OAuth access token is issued by an authorisation server to a client under a grant defined in RFC 6749. For machine callers the usual grant is client credentials (RFC 6749 section 4.4): the client authenticates to the token endpoint, sends grant_type=client_credentials and an optional scope, and receives a token with an expires_in value. The token is presented to the resource server as Authorization: Bearer <token>, or as Authorization: DPoP <token> when bound to a key under RFC 9449. The resource server validates it locally (signed JWT access tokens) or through introspection.
The model difference is separation of duties. With a key, the API that checks the credential also issued it and holds it for the life of the integration. With OAuth, a dedicated authorisation server authenticates the client, applies policy (scopes, audience, lifetime) and issues a short-lived token that the API only has to verify.
Side-by-side comparison
| Property | Static API key | OAuth access token (client credentials) |
|---|---|---|
| Standard | None; provider-specific | RFC 6749, RFC 6750, RFC 9700 (BCP 240) |
| Typical lifetime | Months or years, until revoked | Minutes to an hour; RFC 6750 recommends one hour or less |
| What it identifies | An account, project or application; sometimes only for quota | A registered client, with claims for issuer, audience, scope and expiry |
| Scope | Whatever the provider supports: often all-or-nothing, sometimes per-API restrictions | Scopes and audience per token; RFC 9700 recommends restricting both |
| Proof of possession | None for plain keys; HMAC-signed schemes keep the secret off the wire | None for bearer tokens; mTLS (RFC 8705) or DPoP (RFC 9449) binds the token to a key |
| Long-lived secret | The key itself, sent on every request | The client credential, sent only to the token endpoint, or replaced by a private key, certificate or federated assertion |
| Revocation | Delete the key; immediate if the API checks a central store | Disable the client; issued JWT tokens stay valid until expiry unless the API introspects |
| Validation cost | Database or cache lookup per request | Local signature check against published keys, or an introspection call |
| Auditability | Key ID in API logs, if logged | Token issuance logged centrally per client, plus client_id and jti in API logs |
| Operational overhead | Low to start, high to rotate at scale | Needs an authorisation server and client library support |
| Leak impact | Full access until someone notices and revokes | Access token: limited window. Client secret: same as a key unless asymmetric or federated |
The last row is the one most comparisons get wrong. Leaking a client secret used with client_secret_basic is as serious as leaking an API key, because the attacker can mint tokens indefinitely. The security gain of OAuth comes from what the client authenticates with. See the Token Lifetime Policy Design Guide.
Choosing: decision guidance
Work through these questions in order. The first one that produces a firm answer usually decides it.
- Does the API need to know which user the call is for? If yes, neither a key nor client credentials is right: use an OAuth user grant (authorisation code with PKCE) or token exchange. OWASP API2:2023 says API keys should only be used for API client authentication, not user authentication. See the OAuth Token Exchange and Delegation Guide.
- Can the caller run without any stored secret? If it runs in a cloud, Kubernetes or CI platform that issues identity tokens, federate that identity into OAuth or the provider's token service and store nothing. See the Workload Identity Federation Guide.
- What can the credential reach? Write access to data, money movement, administrative actions or AI model spend point to short-lived, scoped tokens. Read-only, rate-limited or public data can tolerate a restricted key.
- Who operates the caller? Your own services can adopt OAuth cheaply. External partners and customers with simple tooling may only manage a key; offer OAuth as an option and require it for high-risk scopes.
- Is the "key" actually public? Browser and mobile keys are visible to anyone who looks. Treat them as identifiers, restrict them (referrer, app signature, API list) and never let them authorise sensitive actions.
| Scenario | Recommended credential | Why |
|---|---|---|
| Internal service to internal API, same cloud | Platform identity federated to short-lived tokens | No stored secret; audit by workload |
| Internal service to internal API, cross-environment | Client credentials with private_key_jwt (RFC 7523) or mTLS (RFC 8705) | Asymmetric client credential; scoped, expiring tokens |
| Your workload calling a SaaS API that offers both | OAuth app or client credentials over personal or account keys | Scope, expiry and revocation tied to the app, not a person |
| SaaS API that offers only keys | Restricted key in a secrets manager, one per consumer | No alternative; reduce blast radius and rotate |
| Public web or mobile client for maps or quota | Restricted, non-secret key | Identification only; restrictions do the work |
| Partner integration, low risk, read-only | Key acceptable, OAuth offered | Partner tooling may be limited |
| Payments, open banking or high-value data | Sender-constrained tokens (mTLS or DPoP) | Stolen tokens are useless without the private key |
| AI agent calling tools or APIs | Short-lived, audience-restricted tokens per agent | Agents leak and over-reach; expiry limits damage |
Small organisations: running an authorisation server is real overhead. Use the token services your cloud and SaaS providers already run, and keep restricted keys only where a provider offers nothing else. Large organisations: standardise on one authorisation server pattern for internal APIs and publish a rule that new internal APIs do not accept static keys.
The middle ground: hardened keys and hardened tokens
The choice is not binary. Several patterns borrow strengths from both sides.
- Restricted keys: Google Cloud supports API restrictions (which APIs a key can call) and application restrictions (websites, IP addresses, Android or iOS apps, one type at a time), and states that unrestricted API keys are insecure. GitHub fine-grained personal access tokens are limited to one resource owner and selected repositories, default to a 30-day expiry, and can require organisation approval.
- Signed requests: HMAC request signing, as in AWS Signature Version 4, uses a long-lived secret to sign each request rather than sending the secret itself, so a captured request does not reveal the key. The secret still needs the full lifecycle in the API Key Management Guide.
- Keys exchanged for tokens: the key is used only at a token endpoint and the API sees only short-lived tokens. This is client credentials with
client_secret_basicin all but name, and a sensible first migration step. - Keys bound to identities: Google Cloud authorisation keys are bound to a service account and act like a long-lived access token; Google advises against them for production resource management and blocks their creation by default through
iam.managed.disableServiceAccountApiKeyCreation. - Sender-constrained tokens: mTLS binds the token to the client certificate (
cnfclaim withx5t#S256); DPoP binds it to a key pair (cnfwithjkt) and requires a signedDPoPproof JWT carryinghtm,htu,iat,jtiandathon each call. RFC 9700 says servers SHOULD sender-constrain access tokens. See the DPoP Guide and the mTLS Guide.
| Client authentication to the token endpoint | Secret type | Leak consequence |
|---|---|---|
client_secret_basic / client_secret_post (RFC 6749 section 2.3.1) | Shared secret | Attacker mints tokens until the secret is rotated |
private_key_jwt (RFC 7523 assertion, client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer) | Private key, never sent | Only if the private key itself is stolen |
tls_client_auth / self_signed_tls_client_auth (RFC 8705) | Certificate private key | Only if the private key is stolen; tokens can be certificate-bound |
| Federated workload assertion | None stored; platform-issued token | Requires compromise of the workload or platform |
Migrating from API keys to OAuth tokens
- Inventory callers: list every key the API accepts with owner, consuming workload, last use and the endpoints it calls. Keys with no owner or no recent use are revocation candidates, not migration candidates.
- Define scopes from real use: derive scopes from the endpoints each key actually calls, and set the audience to the API. Avoid one "full access" scope that recreates the key.
- Stand up token issuance: register one OAuth client per consuming workload, prefer
private_key_jwt, mTLS or federation, and set a short access token lifetime. Do not issue refresh tokens for client credentials; RFC 6749 says one SHOULD NOT be included. - Accept both in parallel: the API validates tokens (signature,
iss,aud,exp, scope) and still accepts keys, logging every key use. Returninvalid_token(401) andinsufficient_scope(403) per RFC 6750 so clients can debug. - Migrate in rings: internal services first, then partners on a published timeline. Track the share of traffic on tokens per consumer.
- Freeze and retire keys: stop issuing new keys, then disable migrated keys (disable first, delete later, so rollback is a re-enable) and finally remove key validation code.
- Roll back safely: keep key acceptance behind a feature flag per consumer during the window, so a broken client can be returned to its key in minutes without reopening keys for everyone.
Detection and measurement
- Log what identifies the caller: key ID (never the key) or
client_id,jti, scope, source IP and user agent on every API request; token issuance events at the authorisation server. - Alert on drift: a key or client used from a new network or ASN, a sudden volume spike, use of a scope never used before, tokens presented to the wrong audience, and token requests failing client authentication repeatedly.
- Watch for leaked credentials: secret scanning on code, CI logs and containers for both API keys and client secrets. See the Git Secret Scanning Guide.
| Metric | What good looks like |
|---|---|
| Share of internal API calls authenticated with short-lived tokens | Rising towards 100% |
| Static keys older than policy maximum (for example 90 days) | Zero, or each with a documented exception |
| OAuth clients using shared secrets rather than asymmetric or federated credentials | Falling each quarter |
| Median access token lifetime for machine clients | One hour or less |
| Keys or tokens with full-access scope | Zero for new integrations |
| Time from leaked credential detection to revocation | Hours, not days |
Common failure patterns
- Assuming a "public" key stays harmless: in the Google API Keys Exposure 2026 case, Maps and Firebase keys long treated as non-secret gained Gemini access when the Generative Language API was enabled on the same project, and researchers found 2,863 live exposed keys with that access. A key's power is whatever its project enables, today and later.
- Treating OAuth tokens as automatically safe: in the Salesloft Drift breach, stolen OAuth access and refresh tokens for one integration were used to export Salesforce data in bulk between 8 and 18 August 2025, before all Drift tokens were revoked on 20 August 2025. Bearer tokens stored centrally with long-lived refresh rights are as valuable as keys.
- Swapping a key for a client secret and calling it done: the long-lived secret has moved to the token endpoint with the same leak profile. Plan for asymmetric client authentication or federation.
- Tokens in URLs and logs: RFC 6750 discourages the
access_tokenquery parameter because URLs are logged, and RFC 9700 says clients MUST NOT pass access tokens in URI query parameters. Keys in query strings leak the same way. - No audience check: an API that accepts any valid token from the authorisation server lets a token meant for one service be replayed at another. Validate
audon every request. - Personal tokens for automation: a developer's personal access token in a pipeline ties the integration to that person and their full access. GitHub recommends a GitHub App for organisation automation.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management; IA-9 Service Identification and Authentication | Lifetime, rotation and protection of keys and client credentials |
| NIST SP 800-53 Rev. 5 | SC-8 Transmission Confidentiality and Integrity; SC-23 Session Authenticity | TLS for bearer credentials; binding tokens to sessions or keys |
| NIST SP 800-53 Rev. 5 | AC-3 Access Enforcement; AU-3 Content of Audit Records | Scope and audience enforcement; attributable API logs |
| OWASP API Security Top 10 2023 | API2:2023 Broken Authentication | API keys and OAuth are not user authentication |
| MITRE ATT&CK | T1552.001 Unsecured Credentials: Credentials In Files; T1528 Steal Application Access Token; T1550.001 Use Alternate Authentication Material: Application Access Token | How keys and tokens are stolen and replayed |
| CIS Controls v8.1 | Control 16 Application Software Security | Secure authentication design for in-house APIs |
Practitioner checklist
- Write a standard that says which credential each class of caller must present, using the decision table above.
- For new internal APIs, accept short-lived OAuth tokens only, validated for
iss,aud,expand scope. - Prefer
private_key_jwt, mTLS or workload federation over shared client secrets for machine clients. - Set machine access token lifetimes to one hour or less and do not issue refresh tokens for client credentials.
- Restrict every remaining API key by API, source and consumer, one key per consuming workload.
- Treat browser and mobile keys as public identifiers and keep sensitive APIs off the projects they belong to.
- Sender-constrain tokens with mTLS or DPoP for payments, high-value data and agent access.
- Never send keys or tokens in URL query strings; scrub them from logs.
- Track the migration metrics above and review exceptions quarterly.
Standards and references
- IETF: RFC 6749 The OAuth 2.0 Authorization Framework
- IETF: RFC 6750 OAuth 2.0 Bearer Token Usage
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- IETF: RFC 9449 OAuth 2.0 Demonstrating Proof of Possession (DPoP)
- IETF: RFC 8705 OAuth 2.0 Mutual-TLS Client Authentication and Certificate-Bound Access Tokens
- IETF: RFC 7523 JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants
- OWASP: API Security Top 10 2023, API2 Broken Authentication
- Google Cloud: Manage API keys
Related NHI Mgmt Group resources: OAuth 2.1, GNAP and FAPI Guide · SaaS API Token Security Guide · JWT Security Guide · Token and Session Security Guide · API Security Guide