Oauth
The Patreon API is only accessible by authorizing your request with an access token:
- Creator token: the tokens you can find in the developer portal. Use this if you are only using the API for your own account
- User Oauth: user can login to Patreon and be redirect to your application. Use this to grant access to users to a Patreon only part of your application.
For both types of applications you will need to create a client in the developer portal. Copy the client id and secret and store them in your secrets. You can also find the creator access and refresh tokens in the client information.
Creator token
If you don't need to handle Oauth2 requests, but only your own creator profile, the first example will get you started.
Expiring tokens
It is recommended to sync your token to your database or store it somewhere safe, so the token is not lost. Creator tokens will expire after a certain time (likely a few days to a month), so you will need to refresh the token regularly.
Note: There have been reports that tokens expire when the creator logs in.
import { PatreonCreatorClient } from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
CREATOR_ACCESS_TOKEN: string
CREATOR_REFRESH_TOKEN: string
}
const client = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
token: {
access_token: env.CREATOR_ACCESS_TOKEN,
refresh_token: env.CREATOR_REFRESH_TOKEN,
},
},
})
import { PatreonCreatorClient, PatreonStore } from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
SERVER_TOKEN: string
}
const client = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
},
store: new PatreonStore.Fetch('https://my-server/patreon-token', (url, init) => {
return fetch(url, {
...init,
headers: {
'Authorization': env.SERVER_TOKEN,
}
})
})
})
User oauth2
For handling Oauth2 requests, add a redirectUri
and if specified a state
to the options. Determine the scopes
you will need and request only the scopes that your application requires.
Callback
When a user logins on the Patreon website, they will be redirect to the redirectUri
of your client. With the code
query you can fetch the token for the user with the request url.
WARNING
Note that for handling Oauth2 requests the client will not cache or store the tokens anywhere in case you need to refresh it!
import {
type Oauth2StoredToken,
PatreonOauthScope,
PatreonUserClient,
QueryBuilder,
} from 'patreon-api.ts'
// Required env variables
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
}
declare function storeToken(userId: string, token: Oauth2StoredToken): Promise<void>
// Minimal configuration for handling Oauth2
const userClient = new PatreonUserClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
redirectUri: '<uri>',
scopes: [
PatreonOauthScope.Identity,
PatreonOauthScope.Campaigns,
]
}
})
// The Oauth2 callback request with the code parameter
export const fetch = async (request: Request) => {
const query = QueryBuilder.identity.setAttributes({
user: ['is_creator', 'image_url', 'full_name'],
})
// Instance will have the token associated with
const instance = await userClient.createInstance(request)
// No token is needed since `instance.token` is used
const user = await instance.fetchIdentity(query)
// Check if the user has access
if (!user.data.attributes.is_creator) {
return new Response('This website only allows creators', { status: 403 })
}
// Store the (access &) refresh token if you need to make a request later
await storeToken(user.data.id, instance.token)
return new Response(null, { status: 201 })
}
import {
type Oauth2StoredToken,
PatreonOauthScope,
PatreonUserClient,
QueryBuilder,
} from 'patreon-api.ts'
// Required env variables
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
}
declare function storeToken(userId: string, token: Oauth2StoredToken): Promise<void>
// Minimal configuration for handling Oauth2
const userClient = new PatreonUserClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
redirectUri: '<uri>',
scopes: [
PatreonOauthScope.Identity,
PatreonOauthScope.Campaigns,
]
}
})
export const fetch = async (request: Request) => {
const token = await userClient.fetchToken(request.url)
// or:
// const token = await userClient.fetchToken(request)
// or with a code from the search params:
// const token = await userClient.oauth.getOauthTokenFromCode({ code })
if (!token) return new Response('Failed to fetch token', { status: 500 })
const query = QueryBuilder.identity.setAttributes({
user: ['is_creator', 'image_url', 'full_name'],
})
// Token is required to pass to each API call
const user = await userClient.fetchIdentity(query, {
token,
})
// Check if the user has access
if (!user.data.attributes.is_creator) {
return new Response('This website only allows creators', { status: 403 })
}
// Store the (access &) refresh token if you need to make a request later
await storeToken(user.data.id, token)
return new Response(null, { status: 201 })
}
Redirect
Since the client has already configured a redirect uri, scopes and client information, you can also handle Oauth2 login requests:
import { PatreonOauthScope, PatreonUserClient } from 'patreon-api.ts'
// Required env variables
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
}
// Minimal configuration for handling Oauth2
const client = new PatreonUserClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
redirectUri: '<uri>',
scopes: [
PatreonOauthScope.Identity,
PatreonOauthScope.Campaigns,
]
}
})
export function createLoginRedirect () {
return Response.redirect(client.oauth.oauthUri, 301)
}
import { PatreonOauthScope, PatreonUserClient } from 'patreon-api.ts'
// Required env variables
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
}
// Minimal configuration for handling Oauth2
const client = new PatreonUserClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
}
})
export function createLoginRedirect () {
return Response.redirect(client.oauth.createOauthUri({
redirectUri: '<uri>',
scopes: [
PatreonOauthScope.Identity,
PatreonOauthScope.Campaigns,
],
}), 301)
}
Refresh tokens
To refresh an access token you can use the refreshToken
method on the oauth client. The cached token will be updated to the refreshed token.
Store
If you have configured a token store on the client, calling the refreshToken
method will also call the put
method on the store with the refreshed token.
import { PatreonCreatorClient } from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
CREATOR_ACCESS_TOKEN: string
CREATOR_REFRESH_TOKEN: string
}
const client = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
token: {
access_token: env.CREATOR_ACCESS_TOKEN,
refresh_token: env.CREATOR_REFRESH_TOKEN,
},
},
})
// The cached token is the token from the client options
const clientToken = client.oauth.cachedToken!
const refreshedToken = await client.oauth.refreshToken(clientToken)
Revoke tokens
It is not possible to revoke a token with the documented API.
Validation
Validations are disabled by default and require the compared values (such as scopes or token) to be provided in the options of the client.
Scopes validation
If you are using a user client you can enable scope validation:
- to throw an error on missing scopes
- to log a warning on missing scopes
Before a request is made, the scopes for the url and query will be checked:
- the main resource of a route
- protected relationships / attributes (think of address or email)
See the Patreon documentation for the required scopes.
Token validation
With token validation you can let the client refresh tokens when:
- a token with an
expires_in_epoch
attribute is expired - a token with no expiry returns an unauthorized response
After refreshing, the token will be stored in client.oauth.cachedToken
and updated in the store, if connected.
If no token is provided in the request options, the validation will throw an error.
Store
There are 3 built-in methods of retrieving and storing tokens:
- Manual loading and storing
- Fetch: use an external server that accepts
GET
andPUT
requests - KV: store the (creator) token in a KV-like storage system (present on a lot of edge-runtimes).
import {
PatreonCreatorClient,
PatreonStore,
type PatreonStoreKVStorage,
} from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
// Replace this type with your platform's storage type
kv: PatreonStoreKVStorage
}
const kvStoreClient = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
},
store: new PatreonStore.KV(env.kv, 'patreon_creator_token'),
})
import { PatreonCreatorClient, PatreonStore } from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
SERVER_TOKEN: string
}
const fetchStoreClient = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
},
store: new PatreonStore.Fetch('https://my-website.com/token', (url, init) => {
return fetch(url, {
...init,
headers: {
'Authorization': env.SERVER_TOKEN,
},
})
})
})
import { PatreonCreatorClient } from 'patreon-api.ts'
declare const env: {
CLIENT_ID: string
CLIENT_SECRET: string
}
// Replace with your own store
const customAsyncStorage = new Map()
const customStoreClient = new PatreonCreatorClient({
oauth: {
clientId: env.CLIENT_ID,
clientSecret: env.CLIENT_SECRET,
},
store: {
async get() {
return customAsyncStorage.get('creator_token')
},
async put(value) {
customAsyncStorage.set('creator_token', value)
},
async delete() {
customAsyncStorage.delete('creator_token')
},
async list() {
return [...customAsyncStorage.values()]
},
}
})