Expo Router + Microsoft Entra ID (CIAM) Auth (Working Setup)
## 📌 Introduction
Expo Router + Microsoft Entra ID (CIAM) Auth (Working Setup)
📌 Introduction
This guide walks you through building a secure Expo (React Native) mobile app using Expo Router and Microsoft Entra ID (CIAM) for authentication.
You’ll go from a blank project → fully working OAuth flow using a development build, which is required for authentication to function correctly.
1. 🚀 Project Initialization & Setup
Step 1: Create a New Expo Project
We start with the Expo Router template, which provides a clean file-based routing system.
npx create-expo-app authmobileazure -t expo-router
Navigate into the project:
cd authmobileazure
— -
Step 2: Install EAS CLI (and Why It’s Required)
Install the Expo Application Services CLI:
npm install -g eas-cli
Login to your Expo account:
eas login
❗ Why Not Expo Go?
From the official Expo documentation:
Expo Go cannot be used for OAuth or OpenID Connect apps because it does not support custom app schemes required for authentication redirects.
👉 This is critical.
Authentication requires:
- Custom URI schemes
- Native app configuration
✔️ Solution: Use a Development Build via EAS
— -
Step 3: Configure app.json & Run Prebuild
Update your app.json:
{
“expo”: {
“name”: “authmobileazure”,
“slug”: “authmobileazure”,
“version”: “1.0.0”,
“scheme”: “authmobileazure”,
“ios”: {
“bundleIdentifier”: “com.nick.authmobileazure”
},
“android”: {
“package”: “com.nick.authmobileazure”
},
“plugins”: [“expo-router”]
}
}
Run prebuild to generate native folders:
npx expo prebuild
— -
Step 4: Confirm Android Package Name
Check your package name:
android/app/build.gradle
android {
defaultConfig {
applicationId “com.nick.authmobileazure”
}
}
⚠️ This must match exactly with Azure configuration.
— -
2. 🔐 Azure App Registration & Keystore Setup
Step 1: Register Your App in Azure
Create an App Registration in your Microsoft Entra ID (CIAM) tenant.
- Tenant URL:
[https://###.ciamlogin.com/](https://###.ciamlogin.com/`) - Client ID:
### - Platform: iOS → Bundle ID:
###
— -
Step 2: Generate Android SHA-1 Signature
Navigate to your Java bin directory:
bash cd C:\Program Files\Java\jdk-17\bin
Run:
bash keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64
Default password:
android
Copy the generated SHA-1.
Step 3: Add SHA-1 to Azure
In your Azure App Registration:
- Go to Authentication
- Add Android platform
Enter:
- Package Name:
### - Signature Hash:
###
Click Save
3. 📦 Create a Development Build
Step 1: Install Dependencies
npx expo install expo-auth-session expo-web-browser expo-crypto expo-secure-store @react-native-async-storage/async-storage
Step 2: Build Development APK
bash eas build — profile development — platform android
After build completes:
- Download
.apk - Install on a physical device
4. 🏛️ Final App Architecture
📁 Folder Structure
app/ ├── (tabs)/ │ ├── _layout.tsx │ ├── index.tsx │ └── explore.tsx ├── _layout.tsx ├── login.tsx └── redirect.tsx
5. 🔑 Authentication Flow Implementation
➤ app/login.tsx
Handles login and initiates OAuth flow.
import React from ‘react’; import { View, Button, Text, StyleSheet } from ‘react-native’; import { useAuthRequest, makeRedirectUri } from ‘expo-auth-session’; import AsyncStorage from ‘@react-native-async-storage/async-storage’;
export default function LoginScreen() { const redirectUri = makeRedirectUri({ scheme: ‘authmobileazure’, path: ‘redirect’, });
const [request, response, promptAsync] = useAuthRequest({
clientId: “###”,
scopes: [‘openid’, ‘profile’, ‘email’, ‘offline_access’],
redirectUri,
usePKCE: true,
}, {
authorizationEndpoint: [https://###.ciamlogin.com/###/oauth2/v2.0/authorize](https://###.ciamlogin.com/###/oauth2/v2.0/authorize`),
});
const handleLogin = async () => { if (request?.codeVerifier) { await AsyncStorage.setItem(‘code_verifier’, request.codeVerifier); } promptAsync(); };
return ( <View style={styles.container}> <View style={styles.card}> <Text style={styles.title}>Welcome</Text> <Text style={styles.subtitle}>Please sign in to continue.</Text> <Button title=”Login with Microsoft” disabled={!request} onPress={handleLogin} /> </View> </View> ); }
➤ app/redirect.tsx
Handles OAuth callback and token exchange.
import React, { useEffect } from ‘react’; import { View, ActivityIndicator, StyleSheet, Text } from ‘react-native’; import { useLocalSearchParams, useRouter } from ‘expo-router’; import { exchangeCodeAsync, makeRedirectUri } from ‘expo-auth-session’; import { useAuth } from ‘./_layout’; import AsyncStorage from ‘@react-native-async-storage/async-storage’;
export default function RedirectScreen() { const { code } = useLocalSearchParams(); const { signIn } = useAuth(); const router = useRouter();
const redirectUri = makeRedirectUri({ scheme: ‘authmobileazure’, path: ‘redirect’, });
useEffect(() => { const exchangeToken = async (authCode: string) => { const codeVerifier = await AsyncStorage.getItem(‘code_verifier’);
if (!codeVerifier) { console.error(“Could not find code_verifier in storage.”); router.replace(‘/login’); return; }
try {
const tokenResponse = await exchangeCodeAsync({
clientId: “###”,
code: authCode,
redirectUri,
extraParams: { code_verifier: codeVerifier },
}, {
tokenEndpoint: [https://###.ciamlogin.com/###/oauth2/v2.0/token](https://###.ciamlogin.com/###/oauth2/v2.0/token`),
});
await AsyncStorage.removeItem(‘code_verifier’);
if (tokenResponse.refreshToken) { signIn(tokenResponse.refreshToken); } else { router.replace(‘/login’); } } catch (error) { console.error(“Token exchange failed:”, error); router.replace(‘/login’); } };
if (typeof code === ‘string’) { exchangeToken(code); } }, [code]);
return ( <View style={styles.container}> <ActivityIndicator size=”large” /> <Text>Finalizing login…</Text> </View> ); }
➤ app/(tabs)/explore.tsx
Displays authenticated state.
import React from ‘react’; import { View, Text, Button, StyleSheet } from ‘react-native’; import { useAuth } from ‘../_layout’;
export default function ExploreScreen() { const { signOut, userToken } = useAuth();
return ( <View style={styles.container}> <View style={styles.card}> <Text style={{ color: ‘#28a745’ }}>Login Successful!</Text> <Text>Welcome to the Explore page.</Text> <Text>Your Saved REFRESH Token:</Text> <Text selectable>{userToken}</Text> <Button title=”Sign Out” onPress={signOut} /> </View> </View> ); }
➤ app/(tabs)/index.tsx
tsx import { StyleSheet, Text, View } from ‘react-native’;
export default function HomeScreen() { return ( <View style={styles.container}> <Text>Home Tab</Text> <Text>This is the main screen after login.</Text> </View> ); }
➤ Layout Files
app/_layout.tsx→ Auth provider (unchanged)app/(tabs)/_layout.tsx→ Tabs navigation (standard Expo Router setup)
메타데이터
- post_id
- 3454cd9a0595
- slug
- expo-router-microsoft-entra-id-ciam-auth-working-setup-3454cd9a0595
- url
- https://medium.com/@nikhilgohil02/expo-router-microsoft-entra-id-ciam-auth-working-setup-3454cd9a0595
- canonical_url
- https://medium.com/@nikhilgohil02/expo-router-microsoft-entra-id-ciam-auth-working-setup-3454cd9a0595
- author_url
- https://medium.com/@nikhilgohil02
- status
- ok
- fetched_at
- 2026-06-15 20:49:13