keycloak_client 1.0.1
keycloak_client: ^1.0.1 copied to clipboard
A Flutter package for Keycloak authentication using the Authorization Code flow.
keycloak_client #
Cross-platform Keycloak authentication for Flutter with:
- Authorization Code + PKCE
- mobile deep links
- desktop loopback callbacks
- web redirect callbacks
- secure credential persistence
- automatic token refresh
- auth and user streams
Install #
dependencies:
keycloak_client: ^1.0.0
Quick Start #
import 'package:keycloak_client/keycloak_client.dart';
final client = KeycloakClient(
config: ClientConfig(
baseUrl: 'https://auth.example.com',
realm: 'my-realm',
clientId: 'my-client',
),
);
Initialize early:
@override
void initState() {
super.initState();
client.initialize();
}
@override
void dispose() {
client.dispose();
super.dispose();
}
Login:
await client.login();
Logout:
await client.logout();
Get a valid access token:
final token = await client.getAuthToken();
Configuration #
Everything is configured through ClientConfig.
final client = KeycloakClient(
config: ClientConfig(
baseUrl: 'https://auth.example.com',
realm: 'my-realm',
clientId: 'my-client',
),
);
Main fields:
baseUrl: Keycloak server rootrealm: Keycloak realm nameclientId: OAuth client IDclientSecret: for confidential clients onlyscopes: defaults toopenid,email,profilelogLevel: package logging verbosity
Platform config defaults:
MobileConfig.redirectUri:myapp://authDesktopConfig.redirectUri:https://winchetechnologies.co.uk/tools/oauth_redirectDesktopConfig.loopbackUri:http://localhost:8765/callbackWebConfig.redirectUri:https://winchetechnologies.co.uk/tools/oauth_redirect
For desktop, these two values have different jobs:
DesktopConfig.redirectUri: the URI sent to KeycloakDesktopConfig.loopbackUri: the local URI the desktop app listens on
Dev Redirect Helper #
Recent Keycloak versions can be awkward about using localhost as an allowed redirect URI. To make local development easier, this package ships with a public redirect endpoint by default:
https://winchetechnologies.co.uk/tools/oauth_redirect
The idea is:
- Register that public URL in Keycloak as a valid redirect URI.
- Use that public URL as
DesktopConfig.redirectUriduring desktop dev. - Keep
DesktopConfig.loopbackUrion a local address such ashttp://localhost:8765/callback. - Keycloak redirects the browser to the public helper page after login.
- That page lets the you enter your loopback uri.
- The page forwards the full callback, including Keycloak query parameters, to the local loopback server.
Web Startup #
Web login is redirect-based. Call handleWebCallback(Uri.base) on startup before rendering the app:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
if (kIsWeb) {
await client.handleWebCallback(Uri.base);
}
runApp(const MyApp());
}
On web, login() redirects the current tab and does not complete before navigation.
Streams #
Auth state:
StreamBuilder<AuthState>(
stream: client.onAuthChange,
builder: (context, snapshot) {
final state = snapshot.data ?? AuthState.unknown;
return Text('$state');
},
);
User info:
StreamBuilder<UserInfo?>(
stream: client.onUserChange,
builder: (context, snapshot) {
final user = snapshot.data;
return Text(user?.username ?? 'No user');
},
);
Platform Setup #
Keycloak #
Register the correct redirect URIs in your Keycloak client.
Typical values:
- Android/iOS:
myapp://auth - Desktop dev:
https://winchetechnologies.co.uk/tools/oauth_redirect - Desktop local listener:
http://localhost:8765/callback - Web dev:
https://winchetechnologies.co.uk/tools/oauth_redirect - Web prod: your real public web callback URL
For desktop dev with the helper endpoint:
redirectUriis the public URL you register in KeycloakloopbackUriis the local listener inside your desktop app- the helper page bridges the public redirect back to the local loopback server
Android #
Add the deep-link intent filter to your MainActivity in android/app/src/main/AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="myapp" android:host="auth"/>
</intent-filter>
</activity>
Also ensure internet permission exists:
<uses-permission android:name="android.permission.INTERNET"/>
Your MobileConfig.redirectUri must match the scheme/host you register here.
iOS #
Add your custom scheme to ios/Runner/Info.plist:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
Only the scheme goes in CFBundleURLSchemes. For the default
MobileConfig.redirectUri of myapp://auth, iOS registers myapp here and
your Dart config keeps the full redirect URI.
macOS / Windows / Linux #
Desktop login uses a system browser plus a local HTTP listener.
Important fields:
DesktopConfig.redirectUri: the redirect URI sent to KeycloakDesktopConfig.loopbackUri: the local URI the desktop app listens on (default:http://localhost:8765/callback)DesktopConfig.loopbackTimeout: how long to wait for the callback
The app always listens on loopbackUri while Keycloak redirects to redirectUri. For local development:
- register
https://winchetechnologies.co.uk/tools/oauth_redirectin Keycloak - keep
loopbackUrion a local port likehttp://localhost:8765/callback - when the helper page opens, enter that local port so it forwards the callback back to your app
Web #
For local development, you can use the same public helper endpoint:
web: const WebConfig(
redirectUri: 'https://winchetechnologies.co.uk/tools/oauth_redirect',
),
For production, use your own public callback URL instead.
Always:
- register the web redirect URI in Keycloak
- call
handleWebCallback(Uri.base)on app startup
Main API #
initialize(): restore any existing sessionlogin(): start authenticationhandleWebCallback(uri): resume a web redirect flowlogout(): clear session and notify Keycloak when possiblegetAuthToken(): return a valid access token ornullreloadUser(): reload profile data from/userinfoonAuthChange: stream ofAuthStateonUserChange: stream ofUserInfo?
Auth States #
AuthState.unknownAuthState.signedOutAuthState.signedInAuthState.sessionExpired
Exceptions #
The package throws typed exceptions:
KeycloakNetworkExceptionKeycloakServerExceptionKeycloakSessionExpiredExceptionKeycloakTimeoutException
Notes #
- Call
initialize()beforelogin(),logout(), orreloadUser() - Credentials are stored with
flutter_secure_storage - User profile data comes from Keycloak's
/userinfoendpoint