← Back to list

Expo Router + Microsoft Entra ID (CIAM) Auth (Working Setup)

## 📌 Introduction

Nick · 2026-04-16 15:32 · 6 claps · 3.4 min read
#expo-azure-auth-setup #expo #auth #microsoft-entra-id #expo-router
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

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.

— -

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