Skip to content
Prev Previous commit
Next Next commit
updates
  • Loading branch information
osamasayed committed Jan 14, 2026
commit 5ccd4f7cb0db8d747b91c758cdb9f4cf37ffb71a
233 changes: 228 additions & 5 deletions docs/oauth2_apis_versioned/1.0.0/o-auth-2-authorize.api.mdx

Large diffs are not rendered by default.

354 changes: 348 additions & 6 deletions docs/oauth2_apis_versioned/1.0.0/oauth-2-token-exchange.api.mdx

Large diffs are not rendered by default.

233 changes: 228 additions & 5 deletions docs/oauth2_apis_versioned/o-auth-2-authorize.api.mdx

Large diffs are not rendered by default.

354 changes: 348 additions & 6 deletions docs/oauth2_apis_versioned/oauth-2-token-exchange.api.mdx

Large diffs are not rendered by default.

631 changes: 603 additions & 28 deletions docs/quickstart/index.md

Large diffs are not rendered by default.

364 changes: 153 additions & 211 deletions docs/tutorials/oidc/example-integration.mdx

Large diffs are not rendered by default.

294 changes: 186 additions & 108 deletions docs/tutorials/oidc/getting-started-with-oauth2.mdx

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion docs/tutorials/oidc/mobile-apps/_intro.mdx
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
Please make sure you read our guide on how to get started with [OAuth2 integration](/docs/tutorials/oidc/getting-started-with-oauth2).
:::tip 🤲 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 →](/docs/tutorials/oidc/getting-started-with-oauth2#-why-use-quran-foundation-authentication)
:::

Please make sure you read our [OAuth2 Quick Start](/docs/tutorials/oidc/user-apis-quickstart) or the [full integration guide](/docs/tutorials/oidc/getting-started-with-oauth2).
217 changes: 209 additions & 8 deletions docs/tutorials/oidc/mobile-apps/react-native.mdx
Original file line number Diff line number Diff line change
@@ -1,20 +1,221 @@
---
sidebar_position: 1
---
import PartialObtainClientCredentials from './_obtain_client_credentials.mdx';
import PartialIntro from './_intro.mdx';

import PartialObtainClientCredentials from "./_obtain_client_credentials.mdx";
import PartialIntro from "./_intro.mdx";

# React Native

<PartialIntro />
<PartialObtainClientCredentials />

## Example
## ⚡ Quick Setup

```bash
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:

```jsx
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>
);
}

We have created an [Example](https://github.com/quran/oauth2-react-native-client-example) OAuth2 React Native client that connects to Quran.Foundation Authorization server.
// 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>
);
}

<div style={{display: 'flex', alignItems: 'center'}}>
<img style={{width: '30%', border: '1px black solid', margin: '0px 10px'}} alt="User is not logged in yet" src="/img/mobile-auth/logged_out.png" width="200px" height="100px"/>
<img style={{width: '30%', border: '1px black solid', margin: '0px 10px'}} alt="User is consenting" src="/img/mobile-auth/consent.png" width="200px" height="100px"/>
<img style={{width: '30%', border: '1px black solid', margin: '0px 10px'}} alt="User is logged in" src="/img/mobile-auth/logged_in.png" width="200px" height="100px"/>
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](https://github.com/quran/oauth2-react-native-client-example) that you can clone and run.

<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "10px",
marginTop: "20px",
}}
>
<img
style={{ width: "30%", border: "1px solid #ccc", borderRadius: "8px" }}
alt="User is not logged in yet"
src="/img/mobile-auth/logged_out.png"
/>
<img
style={{ width: "30%", border: "1px solid #ccc", borderRadius: "8px" }}
alt="User is consenting"
src="/img/mobile-auth/consent.png"
/>
<img
style={{ width: "30%", border: "1px solid #ccc", borderRadius: "8px" }}
alt="User is logged in"
src="/img/mobile-auth/logged_in.png"
/>
</div>

## 🔑 Making API Calls

Once authenticated, use the `accessToken` to call User APIs:

```javascript
// 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:

```javascript
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 });
};
```

:::tip Store Refresh Token Securely
Use `expo-secure-store` to persist the refresh token between app sessions:

```javascript
import * as SecureStore from "expo-secure-store";
await SecureStore.setItemAsync("refreshToken", authTokens.refreshToken);
```

:::
60 changes: 56 additions & 4 deletions docs/tutorials/oidc/openid-connect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,55 @@ In this [guide](/docs/tutorials/oidc/getting-started-with-oauth2), we have brief

Besides OAuth2, Quran.Foundation also supports [OpenID Connect](https://openid.net/connect) standards by providing the [UserInfo Endpoint](/docs/oauth2_apis_versioned/get-oidc-user-info) to get more information about the user.

## OIDC Discovery & JWKS

Quran.Foundation provides standard OIDC Discovery endpoints for automatic configuration:

| Environment | Discovery URL |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Production | [`https://oauth2.quran.foundation/.well-known/openid-configuration`](https://oauth2.quran.foundation/.well-known/openid-configuration) |
| Pre-Production | [`https://prelive-oauth2.quran.foundation/.well-known/openid-configuration`](https://prelive-oauth2.quran.foundation/.well-known/openid-configuration) |

The discovery document includes:

- `issuer` — The issuer identifier (use for `iss` claim validation)
- `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`
- `jwks_uri` — URL to fetch public keys for JWT verification
- Supported scopes, grant types, and signing algorithms

### Verifying ID Tokens

To verify `id_token` signatures:

1. Fetch the JWKS from the `jwks_uri` in the discovery document
2. Use the `kid` (key ID) from the token header to select the correct key
3. Verify the signature using the public key
4. Validate standard claims: `iss`, `aud`, `exp`, `iat`

```javascript
// Example using jose library
import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
new URL("https://oauth2.quran.foundation/.well-known/jwks.json")
);

const { payload } = await jwtVerify(idToken, JWKS, {
issuer: "https://oauth2.quran.foundation/",
audience: "YOUR_CLIENT_ID",
});

console.log("Verified user:", payload.sub);
```

To be able to access OpenID Connect's endpoints, make sure to include `openid` in the list of requested [scopes](/docs/user_related_apis_versioned/scopes). Once this is done, besides `access_token`, the authorization callback will also contain a [JWT](https://www.rfc-editor.org/rfc/rfc7519) `id_token` parameter.

The [ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) (`id_token`) contains information about the user and can be decoded using one of the JWT [libraries](https://jwt.io/libraries) to know more about the identity of the user. Below is an example of a decoded `id_token`:

```
```json
{
"at_hash": "tGJSmRygf5HXuZx1YDP1",
"aud": [
"quran-demo"
],
"aud": ["quran-demo"],
"auth_time": 1675234788,
"email": "xyz@example.com",
"exp": 1677591803,
Expand All @@ -33,3 +72,16 @@ The [ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) (`
"sub": "a4f5a01d-a641-4b23-ba002f704cfaa"
}
```

### Stable User Identifier (`sub`)

The `sub` (subject) claim is the **stable, unique user identifier** you should store in your database. This UUID:

- Never changes for a given user
- Is unique across all Quran.Foundation apps
- Should be used as your primary key for user data
- Is the same across all sessions and devices for that user

:::tip Use `sub` as Your User ID
Don't store email addresses as user identifiers—users can change their email. The `sub` claim is guaranteed to be stable and unique.
:::
Loading