React Native
We've built this for the Ummah so you don't have to. By using our OAuth2, you get:
- Zero user management — No database, no password resets, no account recovery
- Cross-app sync — Users' bookmarks, goals, and streaks sync with Quran.com automatically
- Single Sign-On — One login works across all Quran apps
Please make sure you read our OAuth2 Quick Start or the full integration guide.
Obtaining OAuth 2.0 client credentials
A perquisite to creating client credentials is for your app to have the value of the redirect URL where the user will land after successfully authenticating/logging out implemented.
Once you have the redirect URL, please submit your OAuth2 Application.
⚡ Quick Setup
npx expo install expo-auth-session expo-web-browser expo-crypto
npm install jwt-decode
🚀 Complete Working Example
Copy this into your App.js to get OAuth2 working immediately:
import * as React from "react";
import { Button, Text, View, StyleSheet } from "react-native";
import jwtDecode from "jwt-decode";
import {
useAuthRequest,
exchangeCodeAsync,
revokeAsync,
} from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
WebBrowser.maybeCompleteAuthSession();
// ⚙️ Configuration - Update these values!
const CLIENT_ID = "YOUR_CLIENT_ID"; // From your OAuth application
const REDIRECT_URI = "exp://192.168.x.x:8081"; // Your Expo dev URL or custom scheme
// OAuth2 Endpoints (use prelive for testing)
const discovery = {
authorizationEndpoint: "https://oauth2.quran.foundation/oauth2/auth",
tokenEndpoint: "https://oauth2.quran.foundation/oauth2/token",
revocationEndpoint: "https://oauth2.quran.foundation/oauth2/revoke",
};
export default function App() {
const [authTokens, setAuthTokens] = React.useState(null);
const [request, response, promptAsync] = useAuthRequest(
{
clientId: CLIENT_ID,
scopes: ["openid", "offline_access", "bookmark", "collection", "user"],
redirectUri: REDIRECT_URI,
usePKCE: true, // Required for mobile - no client_secret needed!
},
discovery
);
React.useEffect(() => {
const exchangeFn = async (exchangeTokenReq) => {
try {
const exchangeTokenResponse = await exchangeCodeAsync(
exchangeTokenReq,
discovery
);
const { idToken } = exchangeTokenResponse;
const userProfile = jwtDecode(idToken);
setAuthTokens({ ...exchangeTokenResponse, userProfile });
} catch (error) {
console.error("Token exchange failed:", error);
}
};
if (response) {
if (response.error) {
console.error("Auth error:", response.params.error_description);
return;
}
if (response.type === "success") {
exchangeFn({
clientId: CLIENT_ID,
code: response.params.code,
redirectUri: REDIRECT_URI,
extraParams: { code_verifier: request.codeVerifier },
});
}
}
}, [discovery, request, response]);
const logout = async () => {
if (authTokens?.refreshToken) {
await revokeAsync(
{ clientId: CLIENT_ID, token: authTokens.refreshToken },
discovery
);
}
setAuthTokens(null);
};
// Logged in view
if (authTokens) {
const { userProfile } = authTokens;
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome, {userProfile.first_name || userProfile.name || "User"}!
</Text>
<Text style={styles.email}>{userProfile.email}</Text>
<View style={styles.buttonContainer}>
<Button title="Logout" onPress={logout} />
</View>
</View>
);
}
// Login view
return (
<View style={styles.container}>
<Text style={styles.title}>Quran App</Text>
<View style={styles.buttonContainer}>
<Button
disabled={!request}
title="Login with Quran.com"
onPress={() => promptAsync()}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: 20,
},
title: { fontSize: 28, fontWeight: "bold", marginBottom: 40 },
welcome: { fontSize: 24, fontWeight: "600", marginBottom: 10 },
email: { fontSize: 16, color: "#666", marginBottom: 30 },
buttonContainer: { marginTop: 20, width: "80%" },
});
Example Repository
We have created a complete Example OAuth2 React Native client that you can clone and run.



🔑 Making API Calls
Once authenticated, use the accessToken to call User APIs:
// Example: Fetch user's bookmarks
const fetchBookmarks = async () => {
const response = await fetch(
"https://apis.quran.foundation/auth/v1/bookmarks",
{
headers: {
"x-auth-token": authTokens.accessToken,
"x-client-id": CLIENT_ID,
},
}
);
return response.json();
};
🔄 Token Refresh
Access tokens expire after 1 hour. The refreshToken allows you to get a new access token without re-authenticating:
import { refreshAsync } from "expo-auth-session";
const refreshTokens = async () => {
const newTokens = await refreshAsync(
{ clientId: CLIENT_ID, refreshToken: authTokens.refreshToken },
discovery
);
setAuthTokens({ ...newTokens, userProfile: authTokens.userProfile });
};
Use expo-secure-store to persist the refresh token between app sessions:
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("refreshToken", authTokens.refreshToken);