Skip to main content

React Native

🤲 Why Use Quran Foundation Authentication?

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

Learn more about the benefits →

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.

User is not logged in yetUser is consentingUser is logged in

🔑 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 });
};
Store Refresh Token Securely

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);