LinkedIn OAuth2 Package
A secure, clean, and lightweight Flutter package for implementing LinkedIn OAuth 2.0 authentication and user profile retrieval using OpenID Connect (OIDC).
Features
- Secure OAuth 2.0 flow: Initiates authentication using secure native web browser sessions.
- OpenID Connect (OIDC): Fetches authentic profile data (name, email, profile picture) using the official LinkedIn userinfo endpoint.
- Easy integration: Features a ready-to-use branded LinkedIn sign-in button.
- Comprehensive error handling: Built-in exception types for cancelled, failed, network, and missing code scenarios.
- Standard compliant: Fully respects custom schemes, deep linking, and OAuth specifications.
Installation
Add linkedin_oauth2 to your pubspec.yaml:
dependencies:
linkedin_oauth2: ^0.0.1
Run the package configuration command:
flutter pub get
LinkedIn Developer Portal Setup
- Go to the LinkedIn Developer Portal.
- Create your application and obtain your Client ID.
- Under the "Products" tab, request access to "Sign In with LinkedIn using OpenID Connect". This enables OIDC scopes (openid, profile, email).
- Under the "Auth" tab, configure your "Authorized Redirect URLs".
- Note: LinkedIn only accepts secure HTTPS URLs (e.g.
https://yourdomain.com/callbackorhttps://yourbackend.com/auth/linkedin/callback). It does not accept custom schemes likemyapp://callback.
- Note: LinkedIn only accepts secure HTTPS URLs (e.g.
Handling Redirect URIs on Mobile
Because the LinkedIn Developer Portal only allows standard HTTPS redirect URIs, mobile applications must handle the redirect using one of two options:
Option A: App Links / Universal Links (Direct Deep Linking)
Configure your mobile app to register and intercept an HTTPS redirect domain.
- Register your HTTPS callback URL (e.g.,
https://yourdomain.com/callback) in the LinkedIn Developer Portal. - Setup Android App Links and iOS Universal Links for
yourdomain.com. - Configure your app to listen to redirects targeting this URL. Control will be returned to the app upon loading the URL.
Android Setup for Option A
Add the intent filter to android/app/src/main/AndroidManifest.xml:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="yourdomain.com" android:pathPrefix="/callback" />
</intent-filter>
Host your verification file at https://yourdomain.com/.well-known/assetlinks.json.
iOS Setup for Option A
- Enable the Associated Domains capability in Xcode.
- Add the domain entry:
applinks:yourdomain.com. - Host the verification file at
https://yourdomain.com/.well-known/apple-app-site-association.
Dart Configuration
final signIn = LinkedInSignIn(
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'https://yourdomain.com/callback',
customScheme: 'https',
);
Option B: Backend Redirection Bridge (Recommended for Simplicity)
Avoid setting up Universal/App Links by routing the redirect through a backend server endpoint which redirects back to the mobile app's custom URL scheme.
- Register your backend endpoint (e.g.,
https://api.yourbackend.com/auth/linkedin/callback) in the LinkedIn Developer Portal. - When LinkedIn redirects the browser to your backend, your server responds with an HTTP 302 redirect pointing to your app's custom scheme URL (e.g.,
myapp://callback?code=CODE&state=STATE). - The mobile OS intercepts the custom scheme and returns control to your Flutter app.
Android Setup for Option B
Register your custom scheme in android/app/build.gradle:
android {
defaultConfig {
manifestPlaceholders = [
appAuthRedirectScheme: 'myapp'
]
}
}
iOS Setup for Option B
Add the custom URL scheme to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Backend Redirection Code Example (Node.js / Express)
app.get('/auth/linkedin/callback', (req, res) => {
const code = req.query.code;
const state = req.query.state;
if (!code) {
return res.status(400).send('Authorization code missing');
}
// Issue 302 redirect back to the mobile app's custom URI scheme
res.redirect(`myapp://callback?code=${code}&state=${state || ''}`);
});
Dart Configuration
final signIn = LinkedInSignIn(
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'https://api.yourbackend.com/auth/linkedin/callback',
customScheme: 'myapp',
);
Usage Examples
1. Branded Sign-in Button
Include the pre-styled LinkedInSignInButton in your UI:
LinkedInSignInButton(
onPressed: () {
// Trigger login flow
executeLinkedInLogin();
},
)
2. Full Authentication Flow
Perform authorization and profile retrieval:
import 'package:linkedin_oauth2/linkedin_oauth2.dart';
final signIn = LinkedInSignIn(
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'https://api.yourbackend.com/auth/linkedin/callback', // Backend Redirect Bridge
customScheme: 'myapp',
);
Future<void> executeLinkedInLogin() async {
try {
// 1. Get Authorization Code
final authResult = await signIn.getAuthCode(
scopes: ['openid', 'profile', 'email'],
state: 'secure_random_state_string',
);
print('Authorization code received: ${authResult.code}');
// 2. Exchange authorization code for an Access Token on your backend
final accessToken = await exchangeCodeWithBackend(authResult.code);
// 3. Retrieve User Profile Details
final profile = await signIn.getUserProfile(accessToken);
print('Hello, ${profile.name}');
print('Email: ${profile.email}');
print('Picture URL: ${profile.picture}');
} on LinkedInAuthCancelledException {
print('User cancelled the sign in flow');
} on LinkedInAuthFailedException catch (e) {
print('Authentication failed: ${e.message}');
} on LinkedInAuthNetworkException {
print('Network connection error occurred');
} on LinkedInAuthException catch (e) {
print('Auth error: ${e.message}');
}
}
Future<String> exchangeCodeWithBackend(String code) async {
// Call your backend API and return the access_token string
return 'access_token_retrieved_from_backend';
}
Exception Types
All exceptions inherit from LinkedInAuthException:
LinkedInAuthCancelledException: Thrown when the user closes the login window or sheet before completing the login flow.LinkedInAuthFailedException: Thrown when the flow fails due to a protocol, configuration, or server error.LinkedInAuthCodeMissingException: Thrown when the redirect URL loads successfully but does not contain thecodeparameter.LinkedInAuthNetworkException: Thrown when a connection issue occurs during the request.
Testing and Mocking
The LinkedInSignIn client supports dependency injection for unit testing. You can supply a custom repository to mock network and authentication logic:
import 'package:linkedin_oauth2/linkedin_oauth2.dart';
class MockLinkedInAuthRepository implements LinkedInAuthRepository {
@override
Future<LinkedInAuthResult> getAuthorizationCode({
required String clientId,
required String redirectUri,
required List<String> scopes,
required String customScheme,
String? state,
}) async {
return const LinkedInAuthResult(code: 'mock_code', state: 'mock_state');
}
@override
Future<LinkedInUser> getUserProfile(String accessToken) async {
return const LinkedInUser(
sub: '123456',
name: 'John Doe',
email: 'john.doe@example.com',
);
}
}
void main() {
final mockClient = LinkedInSignIn(
clientId: 'test_client_id',
redirectUri: 'myapp://callback',
customScheme: 'myapp',
repository: MockLinkedInAuthRepository(),
);
}
License
This project is licensed under the MIT License. See the LICENSE file for details.