A simple, lightweight, and fully customizable React Native package for integrating LinkedIn OAuth2 authentication into your mobile apps. Built with TypeScript and designed for ease of use.
npm install react-native-linkedin-oauth2or
yarn add react-native-linkedin-oauth2This package requires the following peer dependencies:
npm install react-native-webview react-native-safe-area-contextor
yarn add react-native-webview react-native-safe-area-contextFor iOS, run:
cd ios && pod installBefore using this package, you need to set up an OAuth2 application in the LinkedIn Developer Console:
-
Create a LinkedIn App
- Go to LinkedIn Developers
- Click "Create app"
- Fill in the required information
-
Configure OAuth Settings
- Navigate to the "Auth" tab in your app settings
- Add your redirect URI (e.g.,
https://yourapp.com/auth/linkedin/callback) - Note: The redirect URI must match exactly what you use in the component
-
Get Your Credentials
- Copy your Client ID from the app settings
- Copy your Client Secret (keep this secure!)
-
Request Scopes
- In the "Auth" tab, request the scopes you need
- Common scopes:
openid,profile,email - Additional scopes may require LinkedIn approval
Here's a simple example to get you started:
import React, { useState } from 'react';
import { View, Button, Text } from 'react-native';
import { LinkedInModal, LinkedInProfile } from 'react-native-linkedin-oauth2';
const App = () => {
const [showModal, setShowModal] = useState(false);
const [user, setUser] = useState<LinkedInProfile | null>(null);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{user ? (
<View>
<Text>Welcome, {user.name}!</Text>
<Text>Email: {user.email}</Text>
</View>
) : (
<Button
title="Sign in with LinkedIn"
onPress={() => setShowModal(true)}
/>
)}
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="https://yourapp.com/auth/linkedin/callback"
onSuccess={profile => {
console.log('Login successful:', profile);
setUser(profile);
setShowModal(false);
}}
onError={error => {
console.error('Login error:', error);
setShowModal(false);
}}
onClose={() => setShowModal(false)}
/>
</View>
);
};
export default App;| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
isVisible |
boolean |
β | - | Controls the visibility of the modal |
clientId |
string |
β | - | Your LinkedIn Client ID |
clientSecret |
string |
- | Required unless logout or secureMode is true |
|
redirectUri |
string |
β | - | The redirect URI registered in LinkedIn Developer Console |
secureMode |
boolean |
β | false |
If true, returns auth code instead of handling secret |
scope |
string |
β | 'openid email profile' |
Space-separated list of OAuth scopes |
onSuccess |
(data: any) => void |
β | () => {} |
Callback invoked when authentication succeeds (returns LinkedInProfile normally, or string (authCode) if secureMode is true) |
onError |
(error: Error) => void |
β | () => {} |
Callback invoked when an error occurs |
onClose |
() => void |
β | () => {} |
Callback invoked when the modal is closed |
onLogout |
() => void |
β | () => {} |
Callback invoked when logout completes |
logout |
boolean |
β | false |
If true, shows LinkedIn logout page instead of login |
closeOnSuccess |
boolean |
β | true |
Automatically close modal after successful login |
renderHeader |
(props: { onClose: () => void }) => ReactNode |
β | - | Custom header component |
renderLoading |
() => ReactNode |
β | - | Custom loading indicator |
containerStyle |
StyleProp<ViewStyle> |
β | - | Style for the container SafeAreaView |
wrapperStyle |
StyleProp<ViewStyle> |
β | - | Style for the wrapper View |
modalProps |
Partial<Omit<ModalProps, 'visible'>> |
β | { animationType: 'slide' } |
Additional props for React Native Modal |
The LinkedInProfile interface represents the user data returned after successful authentication:
interface LinkedInProfile {
sub: string; // Unique user identifier
name: string; // Full name
given_name: string; // First name
family_name: string; // Last name
picture: string; // Profile picture URL
email?: string; // Email (if 'email' scope requested)
email_verified?: boolean; // Email verification status
locale: {
country: string; // Country code (e.g., 'US')
language: string; // Language code (e.g., 'en')
};
[key: string]: any; // Additional fields
}The LinkedInTokenResponse interface represents the OAuth token response:
interface LinkedInTokenResponse {
access_token: string; // Access token for API requests
expires_in: number; // Token expiration time in seconds
scope: string; // Granted scopes
token_type: string; // Token type (usually 'Bearer')
id_token?: string; // ID token (if OpenID scope requested)
}Request additional permissions by specifying custom scopes:
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="YOUR_REDIRECT_URI"
scope="openid profile email w_member_social" // Request posting permissions
onSuccess={profile => console.log(profile)}
onError={error => console.error(error)}
onClose={() => setShowModal(false)}
/>Provide your own header component:
const CustomHeader = ({ onClose }: { onClose: () => void }) => (
<View
style={{
height: 60,
backgroundColor: '#0077B5',
justifyContent: 'center',
alignItems: 'center',
}}
>
<TouchableOpacity
onPress={onClose}
style={{ position: 'absolute', right: 20 }}
>
<Text style={{ color: 'white', fontSize: 16 }}>β</Text>
</TouchableOpacity>
<Text style={{ color: 'white', fontSize: 18, fontWeight: 'bold' }}>
Sign in with LinkedIn
</Text>
</View>
);
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="YOUR_REDIRECT_URI"
renderHeader={props => <CustomHeader {...props} />}
onSuccess={profile => console.log(profile)}
onError={error => console.error(error)}
onClose={() => setShowModal(false)}
/>;Customize the loading experience:
const CustomLoading = () => (
<View
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 119, 181, 0.1)',
}}
>
<ActivityIndicator size="large" color="#0077B5" />
<Text style={{ marginTop: 10, color: '#0077B5' }}>
Connecting to LinkedIn...
</Text>
</View>
);
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="YOUR_REDIRECT_URI"
renderLoading={() => <CustomLoading />}
onSuccess={profile => console.log(profile)}
onError={error => console.error(error)}
onClose={() => setShowModal(false)}
/>;Log out users from LinkedIn:
const [showLogoutModal, setShowLogoutModal] = useState(false);
<LinkedInModal
isVisible={showLogoutModal}
logout={true}
onLogout={() => {
console.log('User logged out');
setShowLogoutModal(false);
}}
onClose={() => setShowLogoutModal(false)}
/>;Customize the modal appearance:
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="YOUR_REDIRECT_URI"
containerStyle={{ backgroundColor: '#f5f5f5' }}
wrapperStyle={{ borderRadius: 10, overflow: 'hidden' }}
modalProps={{
animationType: 'fade',
transparent: true,
}}
onSuccess={profile => console.log(profile)}
onError={error => console.error(error)}
onClose={() => setShowModal(false)}
/>Sometimes you may want to manually control when to close the modal:
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
clientSecret="YOUR_CLIENT_SECRET"
redirectUri="YOUR_REDIRECT_URI"
closeOnSuccess={false} // Don't auto-close
onSuccess={profile => {
console.log('Login successful:', profile);
// Do something with the profile...
// Then manually close when ready
setTimeout(() => setShowModal(false), 2000);
}}
onError={error => console.error(error)}
onClose={() => setShowModal(false)}
/>Here's a full-featured example with all the props and callbacks included:
import React, { useState } from 'react';
import {
View,
Button,
Text,
Image,
StyleSheet,
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import { LinkedInModal, LinkedInProfile } from 'react-native-linkedin-oauth2';
const LINKEDIN_CONFIG = {
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'https://yourapp.com/auth/linkedin/callback',
};
const App = () => {
const [showModal, setShowModal] = useState(false);
const [showLogoutModal, setShowLogoutModal] = useState(false);
const [user, setUser] = useState<LinkedInProfile | null>(null);
const [error, setError] = useState<string | null>(null);
const handleLogin = () => {
setError(null);
setShowModal(true);
};
const handleSuccess = (profile: LinkedInProfile) => {
console.log('Authentication successful:', profile);
setUser(profile);
setError(null);
};
const handleError = (err: Error) => {
console.error('Authentication error:', err);
setError(err.message);
};
const handleLogout = () => {
setShowLogoutModal(true);
};
const CustomHeader = ({ onClose }: { onClose: () => void }) => (
<View style={styles.header}>
<Text style={styles.headerTitle}>Sign in with LinkedIn</Text>
<TouchableOpacity onPress={onClose} style={styles.closeButton}>
<Text style={styles.closeButtonText}>β</Text>
</TouchableOpacity>
</View>
);
const CustomLoading = () => (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#0077B5" />
<Text style={styles.loadingText}>Authenticating...</Text>
</View>
);
return (
<View style={styles.container}>
{user ? (
<View style={styles.profileContainer}>
<Image source={{ uri: user.picture }} style={styles.profileImage} />
<Text style={styles.userName}>{user.name}</Text>
<Text style={styles.userEmail}>{user.email}</Text>
<Text style={styles.userId}>ID: {user.sub}</Text>
<Button title="Logout" onPress={handleLogout} color="#dc3545" />
</View>
) : (
<View style={styles.loginContainer}>
<Text style={styles.title}>LinkedIn OAuth2 Example</Text>
{error && <Text style={styles.errorText}>{error}</Text>}
<Button
title="Sign in with LinkedIn"
onPress={handleLogin}
color="#0077B5"
/>
</View>
)}
{/* Login Modal */}
<LinkedInModal
isVisible={showModal}
clientId={LINKEDIN_CONFIG.clientId}
clientSecret={LINKEDIN_CONFIG.clientSecret}
redirectUri={LINKEDIN_CONFIG.redirectUri}
scope="openid profile email"
onSuccess={handleSuccess}
onError={handleError}
onClose={() => setShowModal(false)}
renderHeader={props => <CustomHeader {...props} />}
renderLoading={() => <CustomLoading />}
closeOnSuccess={true}
/>
{/* Logout Modal */}
<LinkedInModal
isVisible={showLogoutModal}
logout={true}
onLogout={() => {
console.log('Logout successful');
setUser(null);
setShowLogoutModal(false);
}}
onClose={() => setShowLogoutModal(false)}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
loginContainer: {
alignItems: 'center',
padding: 20,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
color: '#333',
},
profileContainer: {
alignItems: 'center',
padding: 20,
backgroundColor: 'white',
borderRadius: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
profileImage: {
width: 100,
height: 100,
borderRadius: 50,
marginBottom: 15,
},
userName: {
fontSize: 22,
fontWeight: 'bold',
color: '#333',
marginBottom: 5,
},
userEmail: {
fontSize: 16,
color: '#666',
marginBottom: 5,
},
userId: {
fontSize: 12,
color: '#999',
marginBottom: 20,
},
errorText: {
color: 'red',
marginBottom: 10,
textAlign: 'center',
},
header: {
height: 60,
backgroundColor: '#0077B5',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
headerTitle: {
color: 'white',
fontSize: 18,
fontWeight: 'bold',
},
closeButton: {
position: 'absolute',
right: 20,
padding: 10,
},
closeButtonText: {
color: 'white',
fontSize: 24,
fontWeight: 'bold',
},
loadingContainer: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0, 119, 181, 0.1)',
},
loadingText: {
marginTop: 10,
color: '#0077B5',
fontSize: 16,
},
});
export default App;Problem: You're seeing a warning about missing clientId, clientSecret, or redirectUri.
Solution: Ensure required props are provided based on your mode:
<LinkedInModal
isVisible={true}
clientId="YOUR_CLIENT_ID" // β
Required
clientSecret="YOUR_CLIENT_SECRET" // β
Required if secureMode is false
redirectUri="YOUR_REDIRECT_URI" // β
Required
// ...other props
/>Problem: The token exchange fails.
Possible causes:
- Incorrect
clientSecret - Redirect URI mismatch between code and LinkedIn Developer Console
- Expired or invalid authorization code
Solution:
- Verify your credentials in the LinkedIn Developer Console
- Ensure the
redirectUriprop matches exactly what's registered in your app settings - Check that your app is not in a restricted mode in the developer console
Problem: Unable to connect to LinkedIn servers.
Solution:
- Check your internet connection
- Ensure your app has network permissions (especially on Android)
- Verify that LinkedIn's OAuth endpoints are accessible from your network
Problem: The user.email field is undefined after successful login.
Solution:
- Make sure you've requested the
emailscope:<LinkedInModal scope="openid profile email" />
- Verify that the
emailscope is approved in your LinkedIn app settings
Problem: The WebView appears blank or doesn't load.
Solution:
- Ensure
react-native-webviewis properly installed - For iOS, run
cd ios && pod install - Check that you've linked the library correctly (this should be automatic with auto-linking)
Problem: The modal doesn't look right or doesn't fit the screen.
Solution:
-
Ensure
react-native-safe-area-contextis installed and configured -
Wrap your root app component with
SafeAreaProvider:import { SafeAreaProvider } from 'react-native-safe-area-context'; const App = () => ( <SafeAreaProvider>{/* Your app content */}</SafeAreaProvider> );
Caution
Client Secret Exposure: The clientSecret is included in your React Native app bundle, which means it can be extracted by determined users. For maximum security, consider implementing a backend proxy that handles the token exchange instead of doing it directly in the mobile app.
Instead of passing the clientSecret directly to the mobile app, use secureMode:
<LinkedInModal
isVisible={showModal}
clientId="YOUR_CLIENT_ID"
// No clientSecret needed!
redirectUri="https://yourapp.com/auth/linkedin/callback"
secureMode={true}
onSuccess={async authCode => {
// 1. Send authCode to your secure backend
const response = await fetch('https://your-backend.com/api/linkedin/auth', {
method: 'POST',
body: JSON.stringify({ code: authCode }),
});
// 2. Backend exchanges code for token using clientSecret
// 3. Return the profile data (or your own JWT) to mobile app
const data = await response.json();
}}
onClose={() => setShowModal(false)}
/>This way, the clientSecret stays completely secure on your server.
This package is written in TypeScript and includes full type definitions. No need for @types/ packages!
Exported Types:
LinkedInProfile- User profile data structureLinkedInTokenResponse- OAuth token response structureLinkedInModalProps- Component props interface
import {
LinkedInModal,
LinkedInProfile,
LinkedInTokenResponse,
LinkedInModalProps,
} from 'react-native-linkedin-oauth2';
const handleSuccess = (profile: LinkedInProfile) => {
// TypeScript knows the structure of profile
console.log(profile.name, profile.email);
};- β iOS
- β Android
- β Web (requires different OAuth flow)
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Nikhil Wankhede
- GitHub: @NikhilRW
- Built with react-native-webview
- Uses react-native-safe-area-context
- react-native-webview - WebView component for React Native
- react-native-safe-area-context - Safe area context for React Native
Made with β€οΈ by Nikhil Wankhede