feat: add patreon social provider (#6245)

Co-authored-by: benkingcode <ben@lionfeet.com>
Co-authored-by: Kinfe123 <kinfishtech@gmail.com>
This commit is contained in:
Nico Labarre
2025-12-16 22:57:00 -05:00
committed by GitHub
parent 982a8406ca
commit 07cdd67dae
3 changed files with 91 additions and 1 deletions

View File

@@ -138,12 +138,13 @@ Better Auth provides pre-configured helper functions for popular OAuth providers
- **Microsoft Entra ID (Azure AD)** - `microsoftEntraId(options)`
- **Okta** - `okta(options)`
- **Slack** - `slack(options)`
- **Patreon** - `patreon(options)`
### Example: Using Pre-configured Providers
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { genericOAuth, auth0, hubspot, keycloak, line, microsoftEntraId, okta, slack } from "better-auth/plugins"
import { genericOAuth, auth0, hubspot, keycloak, line, microsoftEntraId, okta, slack, patreon } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
@@ -189,6 +190,10 @@ export const auth = betterAuth({
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
}),
patreon({
clientId: process.env.PATREON_CLIENT_ID,
clientSecret: process.env.PATREON_CLIENT_SECRET,
}),
],
}),
],
@@ -204,6 +209,7 @@ Each provider helper accepts common OAuth options (extending `BaseOAuthProviderO
- **Microsoft Entra ID**: Requires `tenantId` (can be a GUID, `"common"`, `"organizations"`, or `"consumers"`)
- **Okta**: Requires `issuer` (e.g., `https://dev-xxxxx.okta.com/oauth2/default`)
- **Slack**: No additional required fields
- **Patreon**: No additional required fields
All providers support the same optional fields:
- `scopes?: string[]` - Array of OAuth scopes to request

View File

@@ -32,4 +32,5 @@ export {
microsoftEntraId,
} from "./microsoft-entra-id";
export { type OktaOptions, okta } from "./okta";
export { type PatreonOptions, patreon } from "./patreon";
export { type SlackOptions, slack } from "./slack";

View File

@@ -0,0 +1,83 @@
import type { OAuth2Tokens, OAuth2UserInfo } from "@better-auth/core/oauth2";
import { betterFetch } from "@better-fetch/fetch";
import type { BaseOAuthProviderOptions, GenericOAuthConfig } from "../index";
export interface PatreonOptions extends BaseOAuthProviderOptions {}
interface PatreonProfile {
data: {
id: string;
attributes: {
full_name: string;
email: string;
image_url: string;
is_email_verified: boolean;
};
};
}
/**
* Patreon OAuth provider helper
*
* @example
* ```ts
* import { genericOAuth, patreon } from "better-auth/plugins/generic-oauth";
*
* export const auth = betterAuth({
* plugins: [
* genericOAuth({
* config: [
* patreon({
* clientId: process.env.PATREON_CLIENT_ID,
* clientSecret: process.env.PATREON_CLIENT_SECRET,
* }),
* ],
* }),
* ],
* });
* ```
*/
export function patreon(options: PatreonOptions): GenericOAuthConfig {
const defaultScopes = ["identity[email]"];
const getUserInfo = async (
tokens: OAuth2Tokens,
): Promise<OAuth2UserInfo | null> => {
const { data: profile, error } = await betterFetch<PatreonProfile>(
"https://www.patreon.com/api/oauth2/v2/identity?fields[user]=email,full_name,image_url,is_email_verified",
{
method: "GET",
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
},
);
if (error || !profile) {
return null;
}
return {
id: profile.data.id,
name: profile.data.attributes.full_name,
email: profile.data.attributes.email,
image: profile.data.attributes.image_url,
emailVerified: profile.data.attributes.is_email_verified,
};
};
return {
providerId: "patreon",
authorizationUrl: "https://www.patreon.com/oauth2/authorize",
tokenUrl: "https://www.patreon.com/api/oauth2/token",
clientId: options.clientId,
clientSecret: options.clientSecret,
scopes: options.scopes ?? defaultScopes,
redirectURI: options.redirectURI,
pkce: options.pkce,
disableImplicitSignUp: options.disableImplicitSignUp,
disableSignUp: options.disableSignUp,
overrideUserInfo: options.overrideUserInfo,
getUserInfo,
};
}