Reddit OAuth2
Authorization Code Exchange for web and mobile — Reddit app preferences setup, identity scope, no email from Reddit, API reference, and frontend examples.
Overview
Reddit login is optional and controlled by REDDIT_AUTH_ENABLED. When disabled, Reddit endpoints return 503 Service Unavailable. Reddit OAuth2 uses the identity scope and does not expose an email address. The API assigns a stable synthetic email {redditId}@users.noreply.reddit.com for account upsert. Token exchange uses HTTP Basic auth; profile fetch uses Bearer + a required User-Agent. JWTs are never placed in the redirect URL; the API issues a one-time exchangeCode (Redis, 60 seconds). The same POST /auth/exchange endpoint is shared with other social providers.
- • Feature-flagged via environment
- • Custom passport-oauth2 strategy + RedditAuthGuard
- • Profile fetched via GET https://oauth.reddit.com/api/v1/me (Authorization: Bearer + User-Agent)
- • Scope identity — Reddit username and id only; no email
- • Token exchange at https://www.reddit.com/api/v1/access_token uses HTTP Basic auth
- • Synthetic {redditId}@users.noreply.reddit.com — not deliverable
- • Redirect allowlist prevents open redirects
- • Same token response shape as POST /auth/login
Reddit app preferences
Create a web app at reddit.com/prefs/apps. Set the redirect URI to match REDDIT_CALLBACK_URL and request the identity scope during authorization.
- Open https://www.reddit.com/prefs/apps
- Scroll to "developed applications" and click create another app... (or create app)
- Choose type web app
- Enter a name and set redirect uri to your Nest API callback (e.g. http://localhost:3000/api/v1/auth/reddit/callback)
- Copy the client id (under the app name) and client secret
- Ensure REDDIT_CALLBACK_URL matches the redirect uri registered in Reddit
- The API requests scope identity — Reddit does not return email via this scope
Environment variables
Loaded from .env via src/config/reddit-oauth.config.ts (namespace redditOAuth on ConfigService). See .env.example.
REDDIT_AUTH_ENABLED=true REDDIT_CLIENT_ID=your-reddit-client-id REDDIT_CLIENT_SECRET=your-reddit-client-secret REDDIT_CALLBACK_URL=http://localhost:3000/api/v1/auth/reddit/callback REDDIT_REDIRECT_ALLOWLIST=myapp://success,http://localhost:5173/auth/callback REDDIT_OAUTH_DEFAULT_ROLES=
Default roles for new Reddit users
New Reddit users receive roles: [] by default (same as password register). Set REDDIT_OAUTH_DEFAULT_ROLES in .env to a comma-separated list of Role values (e.g. user or user,manager). Existing users linked by redditId keep their current roles.
Sequence
- Client opens GET /api/v1/auth/reddit?redirect=<allowlisted URL>
- User consents on Reddit; browser hits GET /api/v1/auth/reddit/callback
- API exchanges code via HTTP Basic at access_token, calls GET https://oauth.reddit.com/api/v1/me, upserts user, stores exchangeCode in Redis, redirects to redirect?code=...
- Client POSTs { code } to /api/v1/auth/exchange and receives JWTs
API reference
1. Start OAuth
Opens Reddit consent at https://www.reddit.com/api/v1/authorize. Query redirect must match a prefix in REDDIT_REDIRECT_ALLOWLIST (or omit to use the first allowlist entry). Scope: identity. Duration: temporary.
GET /api/v1/auth/reddit?redirect=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback
400 invalid redirect · 503 feature disabled
2. Callback
Handled by the API. Token exchange uses https://www.reddit.com/api/v1/access_token with HTTP Basic auth (client id + secret). Profile is loaded from https://oauth.reddit.com/api/v1/me. Redirects to your app with ?code= (exchange code, not the Reddit authorization code).
GET /api/v1/auth/reddit/callback?code=REDDIT_AUTH_CODE&state=... → 302 Location: http://localhost:5173/auth/callback?code=EXCHANGE_CODE
3. Exchange code
Public endpoint shared with other social providers. Consumes the code (single use) and returns the same shape as login.
POST /api/v1/auth/exchange
Content-Type: application/json
{
"code": "EXCHANGE_CODE"
}{
"user": {
"id": "...",
"email": "[email protected]",
"firstName": "reddit_username",
"lastName": "",
"roles": [],
"redditId": "abc123",
"isActive": true
},
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs..."
}401 invalid/expired/used code · 503 social auth disabled
Frontend — Web (SPA)
Same pattern as other OAuth providers: start login in the browser; your callback route reads code from the query string and calls exchange. The user object includes redditId and a synthetic email — do not use it for outbound mail.
const API = 'http://localhost:3000/api/v1';
const redirect = encodeURIComponent(
'http://localhost:5173/auth/callback'
);
// Full-page redirect to Nest → Reddit → back to SPA
window.location.href = `${API}/auth/reddit?redirect=${redirect}`;// /auth/callback on the SPA
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
if (!code) throw new Error('Missing exchange code');
const res = await fetch('http://localhost:3000/api/v1/auth/exchange', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
if (!res.ok) throw new Error(await res.text());
const { user, accessToken, refreshToken } = await res.json();
// persist tokens as your app requires
sessionStorage.setItem('accessToken', accessToken);
// Reddit has no real email — use redditId as the stable identifier
console.log('signed in', user.redditId);- • Add your frontend callback URL to REDDIT_REDIRECT_ALLOWLIST (e.g. http://localhost:5173/auth/callback)
- • REDDIT_CALLBACK_URL must point at the Nest API, not the SPA
- • Reddit never returns a real email — user.email is synthetic and not deliverable
- • Store accessToken / refreshToken securely (memory + httpOnly cookie patterns as you prefer)
Frontend — Mobile (deep link)
Use a custom scheme (or universal link) in the allowlist. After Reddit OAuth, the API redirects to myapp://success?code=.... The app opens, parses the code, and calls exchange.
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
const API = 'https://api.example.com/api/v1';
const redirect = encodeURIComponent('myapp://success');
async function signInWithReddit() {
const url = `${API}/auth/reddit?redirect=${redirect}`;
await WebBrowser.openAuthSessionAsync(url, 'myapp://success');
}import * as Linking from 'expo-linking';
async function exchangeFromUrl(url) {
const { queryParams } = Linking.parse(url);
const code = queryParams?.code;
if (!code) return;
const res = await fetch(`${API}/auth/exchange`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code }),
});
const data = await res.json();
// data.accessToken, data.refreshToken, data.user
return data;
}
Linking.addEventListener('url', ({ url }) => {
exchangeFromUrl(url);
});- • Register the scheme in the OS / Expo app.json
- • Prefer Linking.addEventListener('url', ...) and getInitialURL()
- • Exchange over HTTPS against your API base URL
- • Identify users by redditId — not by synthetic email
Security notes
- • Redirect URLs must match REDDIT_REDIRECT_ALLOWLIST prefixes
- • Exchange codes are single-use and expire in 60 seconds (Redis)
- • Reddit never provides email — synthetic address is for internal upsert only, not notifications
- • Existing accounts are linked by redditId; email-based linking does not apply to Reddit-only users
- • Reddit API requires a descriptive User-Agent on profile requests
- • Reddit-only users have password null — password login / change-password will fail until a password is set
User model
Postgres entity / interface fields used by Reddit login:
- • redditId — unique nullable Reddit user id (id from /api/v1/me)
- • email — synthetic {redditId}@users.noreply.reddit.com (Reddit does not expose email)
- • password — nullable for OAuth-only accounts
- • isActive — set true for new Reddit users
- • roles — from REDDIT_OAUTH_DEFAULT_ROLES env (default [])
Related
- Social Authentication hub
- Google OAuth2 guide
- Facebook OAuth2 guide
- X / Twitter OAuth2 guide
- GitHub OAuth2 guide
- Figma OAuth2 guide
- LinkedIn OpenID Connect guide
- Slack OpenID Connect guide
- Atlassian OAuth 2.0 (3LO) guide
- GitLab OAuth2 guide
- Bitbucket OAuth2 guide
- Discord OAuth2 guide
- Twitch OAuth2 guide
- Steam OpenID 2.0 guide
- Auth & JWT