Provide a more Dart-ish interface for Keycloak JS Adapter.
It supports the original 3 flavours of constructing a KeycloakInstance
.
// This will find the keycloak.json file in the root path
final keycloak = KeycloakInstance();
// This will load the config file at the given path
final keycloak = KeycloakInstance('other_keycloak.json');
// This will construct with the given map
final keycloak = KeycloakInstance.parameters({
"realm": "demo",
"authServerUrl": "http://localhost:8080/auth",
"clientId": "client"
});
All flows are supported. For example, to initialize a Keycloak instance with implicit flow and login immediately:
try {
final authenticated = await keycloak.init(KeycloakInitOptions(
flow: 'implicit',
onLoad: 'login-required'));
if (authenticated) {
_loadPage();
}
} on catch (e) {
_handleError(e);
}
There is one restriction: KeycloakInitOptions.promiseType
must be 'native'
or leave blank. In order to have Dart's Future
works for all API.
As demonstrated above, all KeycloakInstance
promise based API are converted to Dart's Future
. You can use the Future.then()
too if you want:
keycloak.updateToken(55).then((success) {
if (success) {
print("Token Refreshed!");
} else {
print("Token hasn't expired!");
}
}).catchError((e) {
_handleError(e);
});
There are a few callback function one can listen to, simply assign a function to such setter. Example:
keycloak.onAuthSuccess = () => print('on auth success');
Not all exception thrown by keycloak JS adapter is a valid KeycloakError
. It does threw empty exception for some methods. In Dart, it will became a NullThrownError
.
We can first try to cast the error to KeycloakError
and see if we can display meaningful error message. If it doesn't, we just display the generic error message. Nonetheless, there is an exception thrown and we should handle the situation.
try {
final profile = await keycloak.loadUserProfile();
_displayProfile(profile);
} catch (e) {
final errorMessage = e is KeycloakError
? e.error
: e?.toString() ?? 'Unknown Error';
print(errorMessage);
}
- Must have js_facade_gen installed.
- Go into
/bin
folder and executegenerate_js_interop.sh
:- This script assumed you have keycloak repository cloned locally alongside this project's folder.
- There will be syntax error after the generation (the generator is old and lack of maintenance). You have to repair the generated
/lib/src/js_interop/keycloak.dart
Here are the repair instructions:
-
It mistakenly generate syntax error at ln 316:
abstract class KeycloakInstance<TPromise extends dynamic /*'native'|dynamic*/, undefined> {
To fix it, simply remove the second generic parameter
undefined
, it should be just:abstract class KeycloakInstance<TPromise extends dynamic /*'native'|dynamic*/> {
-
It mistakenly thought there was a Keycloak namespace in the keycloak.js, but there isn't. It result in calling all JS functions with a Keycloak namespace in front e.g.
Keycloak.Keycloak()
. To fix it, replace the first line:@JS("Keycloak")
with empty namespace:
@JS()
-
It mistakenly made all the JavaScript callback functions into a Dart function. To fix them, convert all callback functions to a set function.
All these functions need to be replace, starting from ln 421: (commented out the replaced generated codes)
//external void onReady([bool authenticated]); external set onReady(dynamic func); //external void onAuthSuccess(); external set onAuthSuccess(dynamic func); //external void onAuthError(KeycloakError errorData); external set onAuthError(dynamic func); //external void onAuthRefreshSuccess(); external set onAuthRefreshSuccess(dynamic func); //external void onAuthRefreshError(); external set onAuthRefreshError(dynamic func); //external void onAuthLogout(); external set onAuthLogout(dynamic func); //external void onTokenExpired(); external set onTokenExpired(dynamic func);
-
KeycloakTokenParsed
is using outdated types. This is not a code generator issue but an outdated TypeScript definition issue. PR to fix it is active here. This repair is no longer needed once the fix it in.Replace the type of
realm_access
andresources_access
to the responding class respectively: (commented out the replaced generated codes)ln 278
// external dynamic /*{ roles: string[] }*/ get realm_access; // external set realm_access(dynamic /*{ roles: string[] }*/ v); // external List<String> get resource_access; // external set resource_access(List<String> v); external KeycloakRoles get realm_access; external set realm_access(KeycloakRoles v); external KeycloakResourceAccess get resource_access; external set resource_access(KeycloakResourceAccess v);
ln 288
//dynamic /*{ roles: string[] }*/ realm_access, //List<String> resource_access}); KeycloakRoles realm_access, KeycloakResourceAccess resource_access});
Example are a web page demonstrating most of the functionality you can do with this Keycloak Adapter.
- A local Keycloak server running at
http://localhost:8080
. - A 'demo' realm setup.
- A 'test_alpha' client.
You can run with your own configuration, just make sure you replace the keycloak.json
in the example/
folder.
Run it with webdev serve example:2700
. and visit http://localhost:2700
.
This example shows:
- Constructing
KeycloakInstance
- All 3 flows initializations.
- 'login-required' initialization.
- Acquiring user profile, realm access and client access.
- Update token.
- Future APIs.
- Register callbacks.
All tests assumes:
-
A local Keycloak server running at
http://localhost:8080
. -
A 'demo' realm setup.
-
2 clients:
- test_alpha
- test_beta
Just run command pub run build_runner test
.