OAuth 2.0 is the protocol that lets users authorize your application to act on their behalf — posting to Instagram, reading LinkedIn data, accessing YouTube analytics — without ever sharing their password with you. It's the standard behind every "Connect with Google" or "Authorize on Instagram" button you've clicked.
The core problem OAuth solves
Before OAuth, the only way to give a third-party app access to your social media account was to hand over your username and password. The app would log in as you. This was a disaster: the app had full access to everything, you couldn't revoke it without changing your password, and if the app was breached, your credentials were exposed.
OAuth replaces credential sharing with delegated authorization. The user logs in directly on the platform (Instagram, LinkedIn, etc.), chooses which permissions to grant, and the platform issues an access token to your app. The token is scoped (only the permissions the user approved), time-limited (expires after hours, days, or months), and revocable without affecting the user's account credentials.
The OAuth 2.0 authorization code flow
The standard flow has 4 steps:
- Redirect. Your app sends the user to the platform's authorization URL with your app ID, requested scopes, and a state parameter.
- User grants permission. The user logs in (if not already) and approves the requested scopes on the platform's native screen.
- Callback. The platform redirects back to your
redirect_uriwith an authorizationcode. - Token exchange. Your server exchanges the code for an
access_tokenandrefresh_tokenvia a server-side POST. The access token is what you use to make API calls on behalf of the user.
Using Aether Connect Links instead of DIY OAuth
Implementing OAuth from scratch for 7 social platforms means 7 OAuth flows, 7 sets of token storage and rotation logic, and 7 separate app registrations. Aether's Connect Links abstract all of this:
// Step 1: Generate the authorization URL
import Aether from "aether";
const aether = new Aether({ apiKey: process.env.AETHER_API_KEY });
// Aether generates the full OAuth URL with PKCE, state, and scopes
const link = await aether.connectLinks.create({
platform: "instagram",
redirectUrl: "https://your-app.com/oauth/callback",
});
// Redirect the user to this URL
// → https://connect.aetherhq.dev/oauth/ig_... → Instagram auth screen
console.log(link.url);
// Store link.id — you'll need it to associate the connected profile
// with the user in your system after they return// Step 2: Handle the OAuth callback
// Your redirect URL receives: ?connected=true&profileId=ig_abc123&platform=instagram
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const profileId = searchParams.get("profileId");
const platform = searchParams.get("platform");
const connected = searchParams.get("connected");
if (connected !== "true" || !profileId) {
return redirect("/connect?error=oauth_failed");
}
// Associate this profileId with the current user in your DB
await db.socialProfiles.create({
data: { userId: session.user.id, profileId, platform },
});
return redirect("/dashboard?connected=true");
}Aether uses PKCE (Proof Key for Code Exchange) automatically for all platforms that require it — Meta APIs mandate PKCE since 2023. You don't need to implement it yourself.
Access tokens and refresh tokens
Access tokens expire. Instagram tokens last 60 days; TikTok tokens last 24 hours for some scopes; LinkedIn tokens last 60 days; YouTube tokens expire in 1 hour but can be refreshed indefinitely. Without a proactive refresh strategy, connected accounts silently stop working when tokens expire.
// What OAuth gives you (Aether manages this internally):
//
// access_token — short-lived (1–60 days depending on platform)
// refresh_token — long-lived, used to get new access tokens
// scopes — permissions the user granted
// expires_at — when the access token expires
//
// Aether stores and rotates tokens automatically.
// Your code only ever sees profileIds, not raw tokens.
// List connected profiles for a user
const profiles = await aether.profiles.list();
// → [{ id: "ig_abc123", platform: "instagram", displayName: "..." }]
// If a token is expired or revoked, the profile status changes to "disconnected"
// Your webhook will fire a profile.disconnected eventWhat you'd build without Aether
The code below illustrates the scope of DIY OAuth implementation for social platforms. Aether replaces every line of it:
// What you'd need to implement without Aether:
//
// 1. Register a Meta Developer App for Instagram
// 2. Handle the OAuth redirect with state + PKCE
// 3. Exchange authorization code for access_token
// 4. Store access_token + refresh_token securely (encrypted at rest)
// 5. Implement token refresh before expiry (60-day tokens for Instagram)
// 6. Handle token revocation (user disconnects from Meta settings)
// 7. Implement separate OAuth flows for each of 7 platforms
// 8. Manage per-platform scope differences
// 9. Handle Meta's separate app requirements per platform
//
// Aether replaces all of this with one connectLinks.create() callOAuth security checklist
- Always validate the
stateparameter on callback to prevent CSRF attacks - Use PKCE for public clients and any flow where the client secret could be exposed
- Store tokens encrypted at rest — never in plaintext in your database
- Implement token refresh before expiry, not after a 401 surprises you in production
- Handle token revocation — users can disconnect your app from platform settings at any time
Next steps
- Instagram API guide — Connect Links walkthrough for Instagram specifically
- Social media API for developers — broader developer overview