Patreon Access
Standalone Patreon membership and tier checking for Unreal Engine, via in-game loopback OAuth.
- No backend required. The token exchange runs client-side against Patreon's public OAuth endpoints.
- No client secret ships. Patreon's token exchange works with
client_idalone; the secret field exists only as a documented-optional hedge and should stay blank in shipped builds. - Project-agnostic. A single Runtime module with no game-side dependencies.
Win64, Mac, Linux. The loopback flow requires a system browser and the ability to bind a localhost port, so the plugin is desktop-only by design.
How it works
UPatreonAccessSubsystem::BeginLogin() opens the player's system browser on Patreon's consent page with a 127.0.0.1 redirect. A temporary local HTTP listener catches the redirect, exchanges the auth code for tokens, fetches the member's identity (entitled tiers with cents amounts), broadcasts the result, and caches it in a plugin-owned save slot for offline grace.
sequenceDiagram
participant Game
participant Browser as System browser
participant Patreon
Game->>Game: BeginLogin() — start loopback listener on 127.0.0.1:6167
Game->>Browser: Open consent URL (client_id, scope, state nonce)
Browser->>Patreon: Player approves access
Patreon->>Game: Redirect to http://127.0.0.1:6167/auth?code=...&state=...
Game->>Game: Validate state nonce (RFC 8252 CSRF guard)
Game->>Patreon: Exchange auth code for tokens (client_id only)
Game->>Patreon: Fetch identity (entitled tiers, cents amounts)
Game->>Game: Cache membership, broadcast OnMembershipUpdated
The flow is OAuth 2.0 authorization-code with a loopback redirect (the RFC 8252 native-app pattern), including the RFC's CSRF safeguard: each login carries a single-use random state nonce that the callback must echo. Forged or spurious localhost requests can neither complete nor cancel a pending login.
Tokens are the player's own and are stored locally on-device only — there is no server-side storage. Token values are never written to logs.
Quick start
- Register an OAuth client at patreon.com/portal → Clients & API Keys.
- Redirect URI:
http://127.0.0.1:6167/auth— must matchLoopbackPort+CallbackPathif you change them.
- Redirect URI:
- Set the Client ID in the editor: Project Settings → Game → Patreon Access → OAuth → Client Id. Leave Client Secret blank.
- Log in: call
BeginLogin()(Blueprint or C++) from your UI, or use thePatreon.Loginconsole command in a non-Shipping build. - Gate content with the pure query functions.
Use HasEntitledAmountAtLeast(Cents) rather than tier-title matching. Cents thresholds survive tier renames on the Patreon page; titles don't.
C++ example
#include "PatreonAccessSubsystem.h"
void UMyMenuWidget::OnPatreonLoginClicked()
{
UPatreonAccessSubsystem* Patreon =
GetWorld()->GetGameInstance()->GetSubsystem<UPatreonAccessSubsystem>();
Patreon->OnMembershipUpdated.AddDynamic(this, &UMyMenuWidget::HandleMembershipUpdated);
Patreon->OnMembershipFailed.AddDynamic(this, &UMyMenuWidget::HandleMembershipFailed);
Patreon->BeginLogin();
}
void UMyMenuWidget::HandleMembershipUpdated(const FPatreonMembership& Membership)
{
// e.g. gate a $15 tier
const bool bPremium = Membership.HighestEntitledAmountCents >= 1500;
RefreshLockedContent(bPremium);
}
Blueprint example
Get the Patreon Access Subsystem from the Game Instance, bind On Membership Updated / On Membership Failed, then call Begin Login. Gate content anywhere with the pure nodes (Has Entitled Amount At Least, Is Active Patron, etc.).
API reference
All API lives on UPatreonAccessSubsystem, a UGameInstanceSubsystem — resolve it via the Game Instance from either Blueprint or C++.
Actions
| Function | Behavior |
|---|---|
BeginLogin() | Starts the loopback listener and opens the browser consent page. Results arrive via the delegates. 120 s timeout. |
RefreshMembership() | Re-fetches membership with the stored token; one silent refresh-token retry on 401. Fails via delegate if not logged in. |
Logout() | Clears tokens, cached membership, and the cache file; broadcasts an empty membership so listeners drop granted state. |
Delegates (BlueprintAssignable)
| Delegate | Signature | Fires |
|---|---|---|
OnMembershipUpdated | (const FPatreonMembership& Membership) | After any successful login/refresh, and at boot from cache/auto-refresh. |
OnMembershipFailed | (EPatreonLoginResult Result, bool bAuthExpired) | On failure. bAuthExpired = true means stored tokens no longer work — prompt a re-login. |
Queries (BlueprintPure)
| Function | Returns |
|---|---|
GetMembership(bool& bValid) | Cached FPatreonMembership; bValid = fetched within CacheLifetimeDays. |
IsActivePatron() | true when patron status is ActivePatron. |
GetHighestEntitledAmountCents() | Highest entitled tier amount in cents. |
HasEntitledAmountAtLeast(int32 Cents) | Recommended gate. true when the member's highest entitled tier meets the threshold. |
HasTierTitled(const FString& Title) | Convenience title match — prefer the cents check. |
IsLoggedIn() | true when an access token is stored. |
IsRequestInFlight() | true while a login/refresh request is running. |
Types
FPatreonMembership
| Field | Type | Meaning |
|---|---|---|
UserId | FString | Patreon user id. |
FullName | FString | Member display name. |
PatronStatus | EPatreonPatronStatus | See enum below. |
EntitledTiers | TArray<FPatreonTier> | All currently entitled tiers. |
HighestEntitledAmountCents | int32 | Highest AmountCents among entitled tiers — the recommended gate value. |
FetchedAtUtc | FDateTime | When this membership was fetched (drives the offline-grace window). |
FPatreonTier
| Field | Type | Meaning |
|---|---|---|
TierId | FString | Stable Patreon tier id — use this for entitlement mappings. |
Title | FString | Display title (can be renamed by the creator at any time). |
AmountCents | int32 | Tier price in cents. |
EPatreonPatronStatus
Unknown · NoPatron · ActivePatron · DeclinedPatron · FormerPatron
EPatreonLoginResult
Success · Cancelled · ListenerFailed · TokenExchangeFailed · IdentityFetchFailed
Settings
Project Settings → Game → Patreon Access. Values persist to DefaultGame.ini.
| Property | Default | Notes |
|---|---|---|
ClientId | (empty) | From your Patreon client registration. Public by nature. |
ClientSecret | (empty) | Optional; leave blank in shipped builds. |
LoopbackPort | 6167 | Must match the registered redirect URI. Range 1024–65535. |
CallbackPath | /auth | Must match the registered redirect URI. |
Scope | identity identity.memberships | OAuth scopes requested. |
AuthorizeURL | Patreon's endpoint | Advanced override. |
TokenURL | Patreon's endpoint | Advanced override — point at your own proxy if Patreon ever requires a confidential client, with zero code changes. |
IdentityURL | Patreon's endpoint | Advanced override. |
UserAgent | PatreonAccess/1.0 | Sent on all requests. |
CacheLifetimeDays | 40 | Offline grace window — outlives a monthly billing cycle so patrons aren't nagged; lapsed members age out naturally. |
bAutoRefreshOnBoot | true | Silently re-fetch membership at startup when tokens exist. |
Patreon's token exchange is documented to work with client_id alone. ClientSecret exists only as a hedge against a future policy change — anything you put there ships in plaintext config, so leave it blank and use a TokenURL proxy instead if a confidential client ever becomes mandatory.
Console commands
Compiled out of Shipping builds.
| Command | Effect |
|---|---|
Patreon.Login | Runs the full browser login flow. |
Patreon.Refresh | Re-fetches membership with stored tokens. |
Patreon.Status | Logs the cached membership — status, tiers, amounts. Useful for grabbing real tier IDs while wiring up an entitlement mapping. |
Patreon.Logout | Clears tokens and cache. |
Caching and offline behavior
- Membership is cached in the plugin-owned save slot
"PatreonAccess", separate from any game save. - A cached membership keeps granting for
CacheLifetimeDays(default 40) without a network connection, so patrons aren't re-prompted every launch. Lapsed members age out naturally when the cache expires. - With
bAutoRefreshOnBootenabled, the subsystem silently re-fetches membership at startup whenever tokens exist and broadcastsOnMembershipUpdatedon success.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Browser opens but the game never receives the callback | Redirect URI on the Patreon client doesn't exactly match http://127.0.0.1:<LoopbackPort><CallbackPath>, or another process holds the port (ListenerFailed). |
OnMembershipFailed with bAuthExpired = true | Stored tokens were revoked or expired past refresh — prompt the player to log in again. |
| Login silently ignored requests to the callback URL | The state nonce didn't match — by design, mismatched callbacks neither complete nor cancel a pending login. Re-run BeginLogin(). |
| Nothing happens after 120 s | Login timeout — the player closed the browser or never completed consent. OnMembershipFailed fires with Cancelled. |
Copyright (c) 2026 Krypsis Syndicate LLC. All Rights Reserved.