Skip to main content

Flutter AppAuth

Community
Integration guide

flutter_appauth plugin wraps the native iOS and Android AppAuth libraries. As a standard OpenID Connect client, it seamlessly integrates with ThunderID via discovery. Once configured, the underlying AppAuth library natively manages the complex authentication flows, including browser hand-offs, PKCE, and token requests. A ready-made sample app is also provided to demonstrate this implementation in action.

flutter_appauth
Plugin by the flutter_appauth authors
Get startedTry the sample appflutter_appauth docs
bash
flutter pub add flutter_appauth

flutter_appauth 12 · Flutter 3.38+ · Android API 24+ · iOS 13+

Get started

~15 min

Works with a new Flutter application or one you already have. Requires a running ThunderID instance; the steps use https://localhost:8090, the default for a local install. The scheme com.example.appauth is a placeholder: use your own, in lowercase, everywhere it appears. To start from working code instead, the ready-made sample app under Sample app below runs every step of this guide.

1

Create the application in ThunderID

A Flutter application is a public client: it has no client secret, and ThunderID requires PKCE for it. The authorization code and refresh token grants are already enabled.

01

Open the Console at https://localhost:8090/console and navigate to Applications

02

Click Add Application and select Flutter

03

Enter a name, select the sign-in options, and select a theme

04

Under Sign-In Approach, select Redirect to ThunderID Gate

05

Under Deep Link / Universal Link, enter com.example.appauth://callback

06

Click Create, then copy the Client ID

Note

Select the redirect approach, not Bring Your Own UI. flutter_appauth signs users in through the system browser, so it needs the ThunderID-hosted sign-in pages.

2

Allow sign-out and user claims

Two settings on the new application. The post-logout redirect URI must match the one the application sends exactly. The attributes decide which claims reach the ID token: with none selected, only sub is returned, even when the profile and email scopes are granted.

01

On the Advanced tab, add com.example.appauth://logout under Post-Logout Redirect URIs and save

02

On the Token tab, open User, then ID Token, and add the attributes you need (for example email and name)

03

Add the same attributes under User Info Endpoint if you call the userinfo endpoint

3

Add flutter_appauth

Create a project first with flutter create, or skip that and add the plugin to the project you already have. The sample stores the refresh token with flutter_secure_storage; use any secure store you prefer.

bash
flutter create --org com.example my_app
cd my_app
flutter pub add flutter_appauth flutter_secure_storage
4

Register the redirect scheme on Android

AppAuth's redirect activity receives the browser's callback through this scheme. It covers both the sign-in and the post-logout redirect, since they share it.

android/app/build.gradle.kts
kotlin
android {
defaultConfig {
manifestPlaceholders.putAll(
mapOf("appAuthRedirectScheme" to "com.example.appauth")
)
}
}
Note

Keep the scheme in lowercase; the plugin's authors report failed redirects otherwise. On a Groovy build.gradle, use manifestPlaceholders += ['appAuthRedirectScheme': 'com.example.appauth'].

5

Let the redirect return to the application on Android

Recent flutter create templates set android:taskAffinity="" on MainActivity. That puts AppAuth's redirect handling in a separate task, so the browser returns to the application but sign-in never completes, and Logcat shows "No stored state - unable to handle response". Remove the attribute.

android/app/src/main/AndroidManifest.xml
xml
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
...>
<!-- No android:taskAffinity="" here. -->
</activity>
6

Register the redirect scheme on iOS

Add the same scheme as a URL type of the Runner target.

ios/Runner/Info.plist
xml
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.example.appauth</string>
</array>
</dict>
</array>
7

Point the application at your ThunderID issuer

AppAuth reads every endpoint from the discovery document, so the issuer, the client ID, and the two redirect URIs are all the configuration there is. A refresh token is issued without the offline_access scope, so the scopes stay at the OpenID Connect basics.

lib/auth_config.dart
dart
class AuthConfig {
static const issuer = 'https://localhost:8090';
static const clientId = '<your-client-id>';
static const discoveryUrl = '$issuer/.well-known/openid-configuration';
static const redirectUrl = 'com.example.appauth://callback';
static const postLogoutRedirectUrl = 'com.example.appauth://logout';
static const scopes = ['openid', 'profile', 'email'];
}
8

Sign in

One call opens the ThunderID sign-in page in the system browser (ASWebAuthenticationSession on iOS, a Custom Tab on Android), then exchanges the authorization code for tokens. AppAuth generates the PKCE verifier and S256 challenge itself; ThunderID supports only S256 and rejects requests without a challenge.

lib/auth_service.dart
dart
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'auth_config.dart';
const appAuth = FlutterAppAuth();
const storage = FlutterSecureStorage();
Future<TokenResponse?> signIn() async {
try {
final response = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
AuthConfig.clientId,
AuthConfig.redirectUrl,
discoveryUrl: AuthConfig.discoveryUrl,
scopes: AuthConfig.scopes,
),
);
await storage.write(key: 'refresh_token', value: response.refreshToken);
return response;
} on FlutterAppAuthUserCancelledException {
return null; // The user closed the browser.
}
}
Note

The response carries the access token, its expiry, the ID token, and the refresh token. AppAuth validates the ID token's issuer, audience, and nonce before returning it.

9

Refresh the tokens

Exchange the refresh token for new tokens before the access token expires. ThunderID rotates refresh tokens: each refresh revokes the one it used, and reusing a revoked token revokes the whole chain. Always store the token from the latest response.

lib/auth_service.dart
dart
Future<TokenResponse> refresh() async {
final response = await appAuth.token(
TokenRequest(
AuthConfig.clientId,
AuthConfig.redirectUrl,
discoveryUrl: AuthConfig.discoveryUrl,
refreshToken: await storage.read(key: 'refresh_token'),
scopes: AuthConfig.scopes,
),
);
await storage.write(key: 'refresh_token', value: response.refreshToken);
return response;
}
Note

Rotation does not extend the session: a new refresh token keeps the original expiry, 24 hours by default. When a refresh fails, sign the user in again.

10

Sign out

Ending the session in the browser signs the user out of ThunderID as well as the application, so the next sign-in asks for credentials again. Passing the ID token as a hint lets ThunderID skip the confirmation prompt.

lib/auth_service.dart
dart
Future<void> signOut(String idToken) async {
try {
await appAuth.endSession(
EndSessionRequest(
idTokenHint: idToken,
postLogoutRedirectUrl: AuthConfig.postLogoutRedirectUrl,
discoveryUrl: AuthConfig.discoveryUrl,
),
);
} finally {
await storage.delete(key: 'refresh_token');
}
}
11

Trust the local certificate

A local ThunderID serves a self-signed certificate from <THUNDERID_HOME>/config/certs/server.cert. Both the system browser and AppAuth's own HTTP client reject it until the device trusts it, and flutter_appauth's allowInsecureConnections option does not help, since it only permits plain http. Run these once per emulator or simulator.

bash
# Forward the emulator's localhost:8090 to your machine.
adb reverse tcp:8090 tcp:8090
# Copy the certificate, then install it on the emulator: Settings > Security & privacy >
# More security & privacy > Encryption & credentials > Install a certificate > CA certificate,
# and select thunderid-dev.crt from Downloads. Older Android versions list
# Encryption & credentials directly under Settings > Security.
adb push <THUNDERID_HOME>/config/certs/server.cert /sdcard/Download/thunderid-dev.crt
Note

Development only. Android apps ignore user-installed certificates unless the debug configuration above opts in, and keeping it in the debug source set leaves release builds untouched. In production, serve ThunderID with a certificate from a public certificate authority and skip this step.

Sample app

A ready-made Flutter application that runs the code above, from the ThunderID samples repository at samples/integrations/flutter-app-auth. Register it in the Console, pass its client ID, and run it.

Try the Flutter AppAuth Sample App

Run the ready-made sample to see sign-in, token refresh, session restore, and sign-out against ThunderID end to end.

3 min read

Endpoint mapping

Where each flutter_appauth call lands on ThunderID. AppAuth resolves every endpoint from the discovery document, so none of these paths appear in your code.

flutter_appauth callThunderID endpointNotes
discoveryUrl/.well-known/openid-configurationFetched before every call. The issuer is the server URL, with no path.
authorizeAndExchangeCode()/oauth2/authorizeOpened in the system browser. A code_challenge (S256) is required for public clients.
authorizeAndExchangeCode()/oauth2/tokenCode exchange with the code_verifier. No client secret is sent.
token()/oauth2/tokenrefresh_token grant. Returns a new refresh token and, with openid, a new ID token.
endSession()/oauth2/logoutOpened in the system browser. post_logout_redirect_uri must be registered.

Requirements

flutter_appauth>= 12.0supported
Flutter>= 3.38.1supported
AndroidAPI 24+supported
iOS13+supported

Beyond this guide

Everything past this configuration is ordinary flutter_appauth and AppAuth, documented by their authors, plus the ThunderID application settings this guide touches.

flutter_appauth README

AppAuth for Android

AppAuth for iOS

ThunderID application settings

Where to get help

Where to get help

Two projects, two places to ask.

Something wrong in this guide?

The guide and the ThunderID configuration it documents are ours, along with the sample application that goes with it.

Something wrong in flutter_appauth?

The plugin itself is maintained by its own authors. Bugs and feature requests belong on their tracker, not ours.

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.