OAuth2 Web Integration Example
This guide walks you through our official OAuth2 Web Example — a production-ready Node.js + Express app with session management, JWT decoding, and proper logout handling.
Try the hosted demo at oauth2-client-example.quran.foundation
Setup
Clone and run in under 2 minutes:
git clone https://github.com/quran/quran-oauth2-client-example.git
cd quran-oauth2-client-example
cp .env.example .env
npm install
npm start
Edit .env with your credentials:
PORT=3000
BASE_PATH=http://localhost:3000
TOKEN_HOST=https://oauth2.quran.foundation
CLIENT_ID=your_client_id
CLIENT_SECRET=your_client_secret
SCOPES=openid offline_access profile email bookmark collection user
SESSION_SECRET=your_random_session_secret
NODE_ENV=development
The example requires valid OAuth2 credentials. Request access here and register http://localhost:3000/callback as your redirect URI.
Quick Start
- Go to http://localhost:3000
- Click "Continue with Quran.Foundation"
- Log in (or create an account) on Quran.com
- Consent to the requested permissions
- You'll be redirected back with your user profile displayed!
What You'll Get
After successful authentication, you'll receive:
| Token | Purpose |
|---|---|
access_token | Use for API calls (valid ~1 hour) |
id_token | Contains user profile info (JWT) |
refresh_token | Get new access tokens without re-login |
expires_in | Seconds until access token expires |
Making API Calls
Use the access_token to call User APIs:
const response = await fetch(
"https://apis.quran.foundation/auth/v1/bookmarks",
{
headers: {
"x-auth-token": access_token,
"x-client-id": process.env.CLIENT_ID,
},
}
);
const bookmarks = await response.json();
Accessing Resources
Once authenticated, explore the User APIs:
- Collections — Create and manage verse collections
- Bookmarks — Save and retrieve bookmarked verses
- Reading Sessions — Track reading progress
- Goals & Streaks — User reading goals
How the Example Works
Dependencies
| Dependency | Description |
|---|---|
| Express | Minimal Node.js web framework |
| simple-oauth2 | OAuth2 client library |
| express-session | Session management |
| jsonwebtoken | JWT decoding for user info |
| Pug | Templating engine |
Key Endpoints
| Endpoint | Description |
|---|---|
/ | Home page (shows login or user profile) |
/auth | Redirects to Quran.Foundation login |
/callback | Handles OAuth2 callback, exchanges code for tokens |
/logout | Clears session and logs out from OAuth2 provider |
Code Walkthrough
1. OAuth2 Client Setup
const { AuthorizationCode } = require("simple-oauth2");
const client = new AuthorizationCode({
client: {
id: process.env.CLIENT_ID,
secret: process.env.CLIENT_SECRET,
},
auth: {
tokenHost: process.env.TOKEN_HOST,
tokenPath: "/oauth2/token",
authorizePath: "/oauth2/auth",
},
});
2. Authorization URL with CSRF Protection
const crypto = require("crypto");
app.get("/auth", (req, res) => {
// Generate cryptographically random state for CSRF protection
const state = crypto.randomBytes(32).toString("hex");
req.session.oauthState = state;
const authorizationUri = client.authorizeURL({
redirect_uri: "http://localhost:3000/callback",
scope: process.env.SCOPES,
state,
});
res.redirect(authorizationUri);
});
Parameters:
redirect_uri— Where to return after login (must match registered URI exactly)scope— Permissions requested. See available scopesstate— CSRF protection (validate on callback)
3. Token Exchange (Callback) with State Validation
app.get("/callback", async (req, res) => {
const { code, state } = req.query;
// Validate state parameter to prevent CSRF attacks
if (!state || state !== req.session.oauthState) {
console.error("State mismatch - possible CSRF attack");
return res.status(403).send("Invalid state parameter");
}
// Clear the stored state after validation
delete req.session.oauthState;
try {
const tokenResponse = await client.getToken({
code,
redirect_uri: "http://localhost:3000/callback",
});
// Store in session
req.session.token = tokenResponse.token;
// Decode ID token for user info
const userInfo = jwt.decode(tokenResponse.token.id_token);
req.session.user = userInfo;
res.redirect("/");
} catch (error) {
console.error("Token exchange failed:", error);
res.status(500).send("Authentication failed");
}
});
4. Logout with Redirect
To redirect users back to your app after logout, include the post_logout_redirect_uri parameter:
app.get("/logout", (req, res) => {
req.session.destroy(() => {
// Build logout URL with redirect back to app
const params = new URLSearchParams({
post_logout_redirect_uri: `${process.env.BASE_PATH}/`,
});
const logoutUrl = `${process.env.TOKEN_HOST}/oauth2/sessions/logout?${params}`;
res.redirect(logoutUrl);
});
});
The post_logout_redirect_uri must be pre-registered in your OAuth2 client's post_logout_redirect_uris configuration. Contact us to update your client settings.
Production Deployment
The example is ready for production deployment. It includes:
- ✅ Secure session configuration with
httpOnlycookies - ✅ Proper proxy trust for reverse proxies (Fly.io, etc.)
- ✅ Environment-based cookie security (
secure: truein production) - ✅ Logout from both local session and OAuth2 provider
Deploy to Fly.io
fly launch
fly secrets set CLIENT_ID=xxx CLIENT_SECRET=xxx SESSION_SECRET=xxx
fly deploy
Next Steps
- Full OAuth2 Guide — Detailed walkthrough with AI prompts
- Mobile Apps — iOS, Android, React Native guides
- User APIs Reference — All available endpoints