OAuth flows for every social media platform compared
Side-by-side breakdown of OAuth implementations across all eight platforms — token lifetimes, refresh mechanics, required scopes, and common pitfalls.
Every platform uses OAuth. None of them agree on how.
If you are building a social media integration, you will authenticate with OAuth 2.0 on every platform. That is where the consistency ends. Token lifetimes range from 15 minutes to 60 days. Some platforms give you refresh tokens automatically, some require a specific scope, one does not offer them at all. One platform requires you to discover the authorization server dynamically per user.
This is the side-by-side comparison we wanted when we started building Postproxy.
The overview
| Platform | Access Token TTL | Refresh Token TTL | PKCE | Refresh Available |
|---|---|---|---|---|
| X (Twitter) | 2 hours | 6 months | Required | Yes (requires offline.access scope) |
| 1–2 hours / 60 days | N/A | No | No (uses token exchange) | |
| 1 hour / 60 days | N/A | No | No (uses token exchange) | |
| 60 days | 365 days | No | Partners only | |
| TikTok | 24 hours | 365 days | Mobile/desktop only | Yes |
| YouTube (Google) | 1 hour | No fixed expiration | Optional | Yes (requires access_type=offline) |
| 30 days | 365 days | No | Yes | |
| Bluesky (AT Protocol) | 5–60 minutes | 2 weeks to unlimited | Required | Yes |
That table hides a lot of complexity. Here is what each platform actually requires.
X (Twitter)
Flow: OAuth 2.0 Authorization Code with PKCE (mandatory).
X requires PKCE on all OAuth 2.0 flows. You generate a code_verifier, derive a code_challenge using S256, and include it in the authorization request. Plain method is not accepted.
Endpoints:
- Authorization:
https://x.com/i/oauth2/authorize - Token:
https://api.x.com/2/oauth2/token
Scopes for posting:
| Scope | Purpose |
|---|---|
tweet.read | Read tweets |
tweet.write | Create and delete tweets |
users.read | Read user profile |
offline.access | Receive a refresh token |
media.write | Upload media |
Refresh mechanics: Access tokens expire after 2 hours. Refresh tokens last 6 months but are single-use — every refresh returns a new refresh token and invalidates the old one. If you fail to store the new token, the session is lost.
Pitfalls:
- If you omit
offline.accessfrom the initial authorization, you get no refresh token. The user must re-authenticate every 2 hours - Confidential clients authenticate with HTTP Basic (
client_id:client_secretbase64-encoded). Public clients passclient_idin the request body - The free API tier returns 403 on
POST /2/tweetseven with correct scopes. You need at least the Basic plan ($200/month) for reliable write access
Flow: OAuth 2.0 Authorization Code Grant.
Facebook does not use refresh tokens. Instead, it uses a token exchange model: you get a short-lived token from the OAuth flow, then exchange it server-side for a long-lived token.
Endpoints:
- Authorization:
https://www.facebook.com/v22.0/dialog/oauth - Token:
https://graph.facebook.com/v22.0/oauth/access_token - Long-lived exchange:
https://graph.facebook.com/v22.0/oauth/access_token?grant_type=fb_exchange_token
Scopes for posting:
| Scope | Purpose |
|---|---|
pages_manage_posts | Publish to Facebook Pages |
pages_show_list | List managed Pages |
pages_read_engagement | Read Page engagement data |
business_management | Manage business assets |
Refresh mechanics: Short-lived tokens last 1–2 hours. Exchange them for long-lived tokens that last 60 days. Long-lived tokens can be re-exchanged once per day (must be at least 24 hours old). Page access tokens derived from a long-lived user token never expire.
Pitfalls:
- You cannot get a long-lived token directly from the OAuth dialog. The exchange must happen server-side
- Page tokens require a two-step process: get the user token, then call
GET /{page-id}?fields=access_token - App Review is required for most permissions beyond
public_profile. Gettingpages_manage_postsapproved requires screencasts and can take weeks - Graph API versions deprecate roughly every two years. Always pin to a specific version
Flow: OAuth 2.0 Authorization Code Grant (via Facebook’s infrastructure).
Instagram uses the same token exchange model as Facebook. The Instagram Basic Display API was sunset in December 2024 — only Business and Creator accounts work via the API now.
Endpoints:
- Authorization:
https://www.facebook.com/v22.0/dialog/oauth(same as Facebook) - Short-lived token:
https://api.instagram.com/oauth/access_token - Long-lived exchange:
https://graph.facebook.com/v22.0/oauth/access_token?grant_type=fb_exchange_token
Scopes for posting:
| Scope | Purpose |
|---|---|
instagram_basic | Read profile and media |
instagram_content_publish | Publish content |
pages_show_list | Required for Instagram Business accounts |
business_management | Manage connected business assets |
Refresh mechanics: Identical to Facebook. Short-lived tokens (1 hour), exchanged for long-lived tokens (60 days), re-exchangeable once per day.
Pitfalls:
- Instagram Business accounts are connected through Facebook Pages. You need both Facebook and Instagram permissions
instagram_content_publishrequires App Review with screencasts demonstrating the full flow- The container-based publishing model means you interact with Facebook’s Graph API, not a separate Instagram API
Flow: OAuth 2.0 Authorization Code Grant.
LinkedIn is straightforward to authenticate but has a significant limitation: most developers do not get refresh tokens.
Endpoints:
- Authorization:
https://www.linkedin.com/oauth/v2/authorization - Token:
https://www.linkedin.com/oauth/v2/accessToken
Scopes for posting:
| Scope | Purpose |
|---|---|
openid | OpenID Connect (required for new apps) |
profile | Basic profile info |
email | Email address |
w_member_social | Post on behalf of a member |
w_organization_social | Post on behalf of an organization (partner approval required) |
Refresh mechanics: Access tokens last 60 days. For most developers, that is the end of the story — when it expires, the user goes through the full OAuth flow again. If the user is still logged into LinkedIn, the consent screen is skipped and the redirect happens silently. Programmatic refresh tokens (365-day TTL) are only available to approved partners.
Pitfalls:
- Scopes are tied to “Products” in the developer portal. You must apply for “Share on LinkedIn” before
w_member_socialbecomes available r_liteprofileandr_emailaddresswere deprecated in August 2023. New apps must useopenid,profile,email- Requesting different scopes than previously granted invalidates all existing access tokens for that user
- Authorization codes expire in 30 minutes
- Access token strings can be up to 1,000 characters. Plan your storage accordingly
TikTok
Flow: OAuth 2.0 Authorization Code Grant. PKCE is required for mobile and desktop apps, optional for web server apps.
Endpoints:
- Authorization:
https://www.tiktok.com/v2/auth/authorize/ - Token:
https://open.tiktokapis.com/v2/oauth/token/
Scopes for posting:
| Scope | Purpose |
|---|---|
user.info.basic | Basic user profile (added by default) |
video.upload | Upload video content |
video.publish | Publish uploaded videos |
video.list | Read user’s video list |
Refresh mechanics: Access tokens expire after 24 hours. Refresh tokens last 365 days. Each refresh may return a different refresh token — you must always store the newly returned token.
Pitfalls:
video.uploadandvideo.publishare separate scopes. Both are needed to post. Uploading alone does not publish- Authorization codes are URL-encoded in the callback. You must decode them before exchanging for tokens
- The
redirect_uriin the token request must exactly match what was used in the authorization request - TikTok uses
client_keyas the parameter name, notclient_id - Both scopes require TikTok approval for the Content Posting API, which can take multiple review rounds
YouTube (Google)
Flow: OAuth 2.0 Authorization Code Grant.
Google’s OAuth is well-documented but has subtle behavior around refresh tokens that catches most developers.
Endpoints:
- Authorization:
https://accounts.google.com/o/oauth2/v2/auth - Token:
https://oauth2.googleapis.com/token
Scopes for posting:
| Scope | Purpose |
|---|---|
youtube.upload | Upload videos |
youtube | Full YouTube account management |
youtube.force-ssl | Read/write over SSL |
youtube.readonly | Read-only access |
All scopes are prefixed with https://www.googleapis.com/auth/.
Refresh mechanics: Access tokens expire after 1 hour. Refresh tokens have no fixed expiration for published production apps, but expire if unused for 6 months, if the user revokes access, or if the maximum of 50 refresh tokens per user per client is exceeded (oldest are silently invalidated).
Pitfalls:
- Google only issues a refresh token on the first authorization. If you lose it, you must pass
prompt=consentto force re-consent access_type=offlineis not the default. Omitting it means no refresh token- Apps in “Testing” mode on Google Cloud Console have tokens that expire in 7 days and are limited to 100 test users
- YouTube API has a daily quota of 10,000 units by default. A single video upload costs 1,600 units, so you get roughly 6 uploads per day before requesting a quota increase
- Scope verification/audit is required for sensitive scopes and can take weeks
Flow: OAuth 2.0 Authorization Code Grant.
Endpoints:
- Authorization:
https://www.pinterest.com/oauth/ - Token:
https://api.pinterest.com/v5/oauth/token
Scopes for posting:
| Scope | Purpose |
|---|---|
boards:read | Read boards |
boards:write | Create, update, delete boards |
pins:read | Read pins |
pins:write | Create, update, delete pins |
user_accounts:read | Read user account info |
Refresh mechanics: Access tokens last 30 days. Refresh tokens last 365 days. The token endpoint uses HTTP Basic authentication (client_id:client_secret base64-encoded).
Pitfalls:
- Scopes are comma-separated in the authorization URL, not space-separated. This differs from most OAuth implementations
- The token endpoint requires Basic auth in the header, not client credentials in the POST body
- Rate limits are per-user and restrictive for bulk operations
Bluesky (AT Protocol)
Flow: OAuth 2.0 Authorization Code with mandatory PKCE (S256), mandatory DPoP (Demonstrating Proof-of-Possession), and mandatory Pushed Authorization Requests (PAR).
This is the most complex OAuth implementation of the group by a significant margin.
Endpoints: Dynamic per user. Bluesky is a federated protocol, so the authorization server depends on which PDS (Personal Data Server) hosts the user’s account. You discover endpoints by resolving the user’s handle to their PDS, then fetching /.well-known/oauth-authorization-server.
Scopes:
| Scope | Purpose |
|---|---|
atproto | Required base scope for all AT Protocol operations |
transition:generic | Transitional scope with full read/write (same as App Password) |
transition:chat.bsky | Access to chat/DM features |
Granular per-Lexicon scopes are being rolled out but not yet finalized for production use.
Refresh mechanics: Access tokens last 5–60 minutes depending on server configuration. Refresh tokens are rotated on every use. Every token request requires a valid DPoP proof — a signed JWT proving possession of the client’s private key. The server issues DPoP nonces that must be included in subsequent proofs.
Pitfalls:
- DPoP is mandatory, not optional. Most generic OAuth libraries do not support it
- PAR is mandatory. You must POST authorization parameters to the PAR endpoint first, receive a
request_uri, then redirect the user with that URI. You cannot put parameters directly on the authorization URL - The
client_idis a URL where your client metadata JSON is hosted publicly. Localhost development requires specialhttp://localhostpatterns - The authorization server is discovered dynamically per user, so your code must handle arbitrary endpoints
- The
transition:genericscope is explicitly temporary and will be replaced by granular scopes. Plan for migration - The Bluesky team recommends using
@atproto/oauth-client-nodeor@atproto/oauth-client-browserrather than building from scratch
The pattern across platforms
A few things become clear when you lay these implementations side by side.
Token storage is not one-size-fits-all. Facebook uses token exchange with no refresh tokens. Google gives you effectively permanent refresh tokens but only issues them once. Bluesky rotates everything aggressively. LinkedIn gives most developers no refresh mechanism at all. A multi-platform token management layer has to handle all of these models.
Scope naming has no standard. X uses short names (tweet.write). Google uses full URLs (googleapis.com/auth/youtube.upload). LinkedIn uses prefixed names (w_member_social). Bluesky uses protocol identifiers (atproto). Pinterest uses resource-based names (pins:write). There is no pattern to memorize — you read the docs for each one.
App review is the real bottleneck. Most platforms require some form of app review or verification before you can access publishing scopes in production. Meta requires screencasts. TikTok requires multiple review rounds. Google requires scope verification. LinkedIn requires product approval. The OAuth implementation might take a day; getting approved to use it can take weeks.
What Postproxy handles
Postproxy manages OAuth for all eight platforms so your system does not have to:
- Eight different OAuth flows with automatic token refresh and renewal
- Token exchange for Meta platforms, PKCE for X and Bluesky, DPoP for Bluesky
- Per-platform scope management and token storage
- Silent re-authentication detection and user notification
- Refresh token rotation tracking across platforms that rotate
Your system authenticates once through Postproxy. We handle every platform-specific OAuth implementation, token lifecycle, and renewal edge case.
Connect your accounts and start publishing through the Postproxy API.