A complete OAuth2 solution for Flutter apps. Handles auth, token storage, and token refresh.
- Handles
dio
client setup - Securely stores tokens
- Automatically refreshes tokens when expired
- Refresh token expiration handler
- Nonce, PKCE, and state verification
- OIDC support
- Endpoint discovery
- Access to the ID token and raw nonce
- Works with Firebase OIDC implicit flow
The most relevant setup information for iOS/Android/web apps is copied below. See the individual plugin readmes for more details:
Set up Universal Links
Set up App Links
android/app/build.gradle
android {
defaultConfig {
minSdk = 23
}
}
In order to capture the callback url, the following activity needs to be added to your AndroidManifest.xml
. Be sure to replace YOUR_CALLBACK_URL_SCHEME_HERE
with your actual callback url scheme.
<manifest>
<application>
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="YOUR_CALLBACK_URL_SCHEME_HERE" />
</intent-filter>
</activity>
</application>
</manifest>
Ensure that the web server serves the Flutter app with a Strict-Transport-Security
header. Firebase Hosting sets this header by default. See the documentation. Here is an example header:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
An endpoint must be created that captures the callback URL and sends it to the application using the JavaScript postMessage
method. In the web
folder of the project, create an HTML file named, e.g. oauth2/callback
with content:
<!DOCTYPE html>
<title>Authentication complete</title>
<p>
Authentication is complete. If this does not happen automatically, please
close the window.
</p>
<script>
function postAuthenticationMessage() {
const message = {
"flutter-web-auth-2": window.location.href,
};
if (window.opener) {
window.opener.postMessage(message, window.location.origin);
window.close();
} else if (window.parent && window.parent !== window) {
window.parent.postMessage(message, window.location.origin);
} else {
localStorage.setItem("flutter-web-auth-2", window.location.href);
window.close();
}
}
postAuthenticationMessage();
</script>
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:oauth_flutter/oauth_flutter.dart';
void main() async {
final client = OAuth2Client(
key: 'fitbit',
// The `baseUrl` is the OAuth `aud` parameter
dio: Dio(BaseOptions(baseUrl: 'https://api.fitbit.com/1/user')),
endpoints: OAuth2Endpoints(
authorization: 'https://fitbit.com/oauth2/authorize',
token: 'https://api.fitbit.com/oauth2/token',
revocation: 'https://api.fitbit.com/oauth2/revoke',
),
// Use `OAuth2Endpoints.base` for services with a consistent base URL
// endpoints: OAuth2Endpoints.base('https://api.fitbit.com/oauth2'),
redirectUri: Uri.parse('https://your-app.com/oauth2/callback'),
// Do not pass client credentials if they are injected by the server
credentials: const OAuth2ClientCredentials(
id: 'your-client-id',
secret: 'your-client-secret',
),
scope: {
'activity',
'heartrate',
'nutrition',
'oxygen_saturation',
'respiratory_rate',
'settings',
'sleep',
'temperature',
'weight',
},
);
final token = await client.authenticate();
debugPrint(token.idToken); // Fitbit doesn't actually support OIDC
final response =
await client.dio.get('/GGNJL9/activities/heart/date/today/1d.json');
debugPrint(response.data);
final isAuthenticated = await client.isAuthenticated();
debugPrint(isAuthenticated.toString());
}