OAuth 2.0 Authentication

Warning Icon

Warning

The password grant type is prohibited in the latest OAuth 2.0 Security Best Current Practice (opens in a new tab) and is not supported by Speakeasy.

Client Credentials Flow

OAuth 2.0 defines several methods for building a request to the tokenUrl endpoint.

Client Authentication MethodDescriptionSpeakeasy Support
client_secret_postThe secret is provided in the request body as application/x-www-form-urlencoded form data
client_secret_basicThe secret is provided in the Authorization header using the Basic authentication scheme✅ with hooks
others-✅ with hooks

Define type: oauth2 and flows: clientCredentials to prompt users for a client ID and client secret when instantiating the SDK. The client credentials flow is used to obtain an access token for API requests.


paths:
/drinks:
get:
operationId: listDrinks
summary: Get a list of drinks.
description: Get a list of drinks, if authenticated this will include stock levels and product codes otherwise it will only include public information.
security:
- clientCredentials:
- read:drinks
tags:
- drinks
components:
securitySchemes:
clientCredentials:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://speakeasy.bar/oauth2/token/
scopes: {}
security:
- clientCredentials:
- read:basic

Global scopes defined for the OAuth 2.0 scheme are requested alongside any operation-specific scopes when making API requests.

To enable client credentials flow in the SDK, add the following to the `gen.yaml`` file:


configVersion: 2.0.0
generation:
auth:
OAuth2ClientCredentialsEnabled: true


import { SDK } from "speakeasy";
async function run() {
const sdk = new SDK({
security: {
clientID: "<YOUR_CLIENT_ID_HERE>",
clientSecret: "<YOUR_CLIENT_SECRET_HERE>",
},
});
const result = await sdk.drinks.listDrinks();
// Handle the result
console.log(result);
}
run();

Custom Refresh Token Flow

To enable custom OAuth refresh token handling, implement security callbacks (opens in a new tab) along with additional configuration outside of the OpenAPI spec.

Step 1: Define OAuth Security in the OpenAPI Spec


/oauth2/token:
get:
operationId: auth
security:
- []
responses:
200:
description: OK
content:
application/json:
schema:
type: object
properties:
access_token: string
required:
- access_token
/example:
get:
operationId: example
responses:
200:
description: OK
components:
securitySchemes:
auth:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://speakeasy.bar/oauth2/token/
scopes: {}
security:
- auth: []

Step 2: Add Callback Function to the SDK

Add a file called oauth.ts (or oauth.py, oauth.go) to implement OAuth token exchange logic.


import * as z from "zod";
import { SDK_METADATA } from "./lib/config";
// TypeScript SDKs use Zod for runtime data validation. We can use Zod
// to describe the shape of the response from the OAuth token endpoint. If the
// response is valid, Speakeasy can safely access the token and its expiration time.
const tokenResponseSchema = z.object({
access_token: z.string(),
expires_in: z.number().positive(),
});
// This is a rough value that adjusts when we consider an access token to be
// expired. It accounts for clock drift between the client and server
// and slow or unreliable networks.
const tolerance = 5 * 60 * 1000;
/**
* A callback function that can be used to obtain an OAuth access token for use
* with SDKs that require OAuth security. A new token is requested from the
* OAuth provider when the current token has expired.
*/
export function withAuthorization(
clientID: string,
clientSecret: string,
options: { tokenStore?: TokenStore; url?: string } = {},
) {
const {
tokenStore = new InMemoryTokenStore(),
// Replace this with your default OAuth provider's access token endpoint.
url = "https://oauth.example.com/token",
} = options;
return async (): Promise<string> => {
const session = await tokenStore.get();
// Return the current token if it has not expired yet.
if (session && session.expires > Date.now()) {
return session.token;
}
try {
const response = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
// Include the SDK's user agent in the request so requests can be
// tracked using observability infrastructure.
"user-agent": SDK_METADATA.userAgent,
},
body: new URLSearchParams({
client_id: clientID,
client_secret: clientSecret,
grant_type: "client_credentials",
}),
});
if (!response.ok) {
throw new Error("Unexpected status code: " + response.status);
}
const json = await response.json();
const data = tokenResponseSchema.parse(json);
await tokenStore.set(
data.access_token,
Date.now() + data.expires_in * 1000 - tolerance,
);
return data.access_token;
} catch (error) {
throw new Error("Failed to obtain OAuth token: " + error);
}
};
}
/**
* A TokenStore is used to save and retrieve OAuth tokens for use across SDK
* method calls. This interface can be implemented to store tokens in memory,
* a shared cache like Redis or a database table.
*/
export interface TokenStore {
get(): Promise<{ token: string; expires: number } | undefined>;
set(token: string, expires: number): Promise<void>;
}
/**
* InMemoryTokenStore holds OAuth access tokens in memory for use by SDKs and
* methods that require OAuth security.
*/
export class InMemoryTokenStore implements TokenStore {
private token = "";
private expires = Date.now();
constructor() {}
async get() {
return { token: this.token, expires: this.expires };
}
async set(token: string, expires: number) {
this.token = token;
this.expires = expires;
}
}

Step 3: Pass Callback Function in SDK Instantiation

Update the README to show how to pass the callback function when instantiating the SDK:


import { SDK } from "speakeasy";
const sdk = new SDK({
security: withAuthorization("client_id", "client_secret"),
});
await s.drinks.listDrinks();

OAuth 2.0 Scopes

Global Security with OAuth 2.0 Scopes

When defining global security settings for OAuth 2.0, the SDK automatically requests the necessary scopes for all operations. This setup is useful for APIs where most endpoints share the same level of access. Global scopes are defined in the OpenAPI specification and applied to all requests unless specifically overridden.

The following OpenAPI definition applies global OAuth 2.0 scopes:


components:
securitySchemes:
oauth2:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://speakeasy.bar/oauth2/token/
scopes:
read: Grants read access
write: Grants write access
security:
- oauth2:
- read # Apply the read scope globally
- write # Apply the write scope globally

The SDK automatically generates tokens with both read and `write`` scopes. When making a request, the SDK checks whether the token contains the required scopes for the operation. If the token lacks the necessary scopes or has expired, a new token is requested with the correct scopes.

In the SDK, global OAuth 2.0 scopes can be defined when the SDK is instantiated:


import { SDK } from "speakeasy";
const sdk = new SDK({
security: {
clientID: "<YOUR_CLIENT_ID_HERE>",
clientSecret: "<YOUR_CLIENT_SECRET_HERE>",
oAuth2Scopes: ["read"], // Global scope applied to all operations by default
},
});

Per-Operation Security with OAuth 2.0 Scopes

For more control over specific API operations, per-operation security settings can be used. This allows different scopes to be applied to individual operations, overriding the global settings.

The following OpenAPI definition applies an operation-specific OAuth scope for the listDrinks operation:


paths:
/drinks:
get:
operationId: listDrinks
summary: Get a list of drinks.
description: Retrieves a list of drinks, requiring the `read` scope.
security:
- oauth2:
- read # Apply the read scope for this operation

In this case, the SDK requests a token with the read scope only when calling the `listDrinks`` operation. If the token does not meet the required scope for the operation or has expired, the SDK regenerates the token with the correct scope.

Here’s how the SDK can be used with per-operation security:


import { SDK } from "speakeasy";
const sdk = new SDK({
security: {
clientID: "<YOUR_CLIENT_ID_HERE>",
clientSecret: "<YOUR_CLIENT_SECRET_HERE>",
},
});
const result = await sdk.drinks.listDrinks({
security: {
oAuth2Scopes: ["read"], // Specify the scope for this operation
},
});