Javascript adapter Hybrid Apps with Capacitor

1,610 views
Skip to first unread message

Hamed KARAMOKO

unread,
Apr 18, 2020, 6:54:55 AM4/18/20
to Keycloak Dev
Hello,

Firstly I want to thank the whole community for the great work achieved to always enhanced Keycloak. :)

As you may know, the ionic team promotes Capacitor, a tools allowing to build cross platform app. They describe it as a successor of Apache Cordova. You can find more information at https://capacitor.ionicframework.com/docs/.

I tried to make work the javascript keycloak adapter configured as 'cordova-native' (as it is the recommended way to do) to secure my angular ionic app using capacitor without success.
After striving to find the issue, I noticed that the 'universalLinks.subscribe' in the loadAdapter method does not kick off even after installing plugins 'cordova-universal-links-plugin'  and after 'cordova-universal-links-plugin-fixed'.

To fixed that, I had the idea to give the Capacitor App API in the init options and used it in the 'loadAdapter' function.

You can find bellow how I implement it : 

keycloak.d.ts

L32 : type KeycloakAdapterName = 'cordova' | 'cordova-native' | 'capacitor' |'default' | any;

interface KeycloakInitOptions {


...

/**

 * Set the capacitor App API handling App state and events.

 *  More information https://capacitor.ionicframework.com/docs/apis/app

*/

capacitorAppApi?: any;


}


keycloak.js


L140 : var adapters = ['default', 'cordova', 'cordova-native', 'capacitor'];


L143 : adapter = loadAdapter(initOptions.adapter, initOptions.capacitorAppApi);


L1283 : function loadAdapter(type, appApi) {


if (type == 'capacitor') {

                loginIframe.enable = false;


                return {

                    login: function(options) {

                        var promise = createPromise(false);

                        var loginUrl = kc.createLoginUrl(options);


                        appApi.addListener('appUrlOpen', (data) => {

                            window.cordova.plugins.browsertab.close();

                            var oauth = parseCallback(data.url);

                            processCallback(oauth, promise);

                        });


                        window.cordova.plugins.browsertab.openUrl(loginUrl);

                        return promise.promise;

                    },


                    logout: function(options) {

                        var promise = createPromise(false);

                        var logoutUrl = kc.createLogoutUrl(options);


                        appApi.addListener('appUrlOpen', (data) => {

                            window.cordova.plugins.browsertab.close();

                            kc.clearToken();

                            promise.setSuccess();

                        });


                        window.cordova.plugins.browsertab.openUrl(logoutUrl);

                        return promise.promise;

                    },


                    register : function(options) {

                        var promise = createPromise(false);

                        var registerUrl = kc.createRegisterUrl(options);


                        appApi.addListener('appUrlOpen', (data) => {

                            window.cordova.plugins.browsertab.close();

                            var oauth = parseCallback(data.url);

                            processCallback(oauth, promise);

                        });


                        window.cordova.plugins.browsertab.openUrl(registerUrl);

                        return promise.promise;


                    },


                    accountManagement : function() {

                        var accountUrl = kc.createAccountUrl();

                        if (typeof accountUrl !== 'undefined') {

                            window.cordova.plugins.browsertab.openUrl(accountUrl);

                        } else {

                            throw "Not supported by the OIDC server";

                        }

                    },


                    redirectUri: function(options) {

                        if (options && options.redirectUri) {

                            return options.redirectUri;

                        } else if (kc.redirectUri) {

                            return kc.redirectUri;

                        } else {

                            return "http://localhost";

                        }

                    }

                }

            }

...
}

In my angular ionic project I have a service called AuthenticationService :

import * as Keycloak from 'keycloak-js';

import { Plugins } from '@capacitor/core';


const { App } = Plugins;


@Injectable({

    providedIn: 'root'

})

export class AuthenticationService {



init(): Promise<any> {


        const keycloakAuth: Keycloak.KeycloakInstance = Keycloak({

            url: environment.keycloakApi,

            realm: environment.keycloak.realm,

            clientId: environment.keycloak.clientId

        });


        …


        return new Promise((resolve, reject) => {

            keycloakAuth.init({

                onLoad: 'login-required',

                adapter: 'capacitor',

                responseMode: 'query',

                redirectUri: environment.keycloak.redirectUri,

                capacitorAppApi: App

            }).success(() => {

                ...

                resolve();

            }).error(() => {

                …

                reject();

            });

        });

    }


}


It works fine so I propose it to the community before doing a pull request.


What do you think about this solution ?


Best regards,

Hamed KARAMOKO 



Jon Koops

unread,
Apr 24, 2020, 8:05:02 AM4/24/20
to Hamed KARAMOKO, Keycloak Dev
Just for your information - I have had similar requests coming in for Keycloak Angular (see: https://github.com/mauriciovigolo/keycloak-angular/issues/244). I think it would be wise to start to allow users to pass their own custom adapter instances so that they could adapt to the environment they are using.

--
You received this message because you are subscribed to the Google Groups "Keycloak Dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to keycloak-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/keycloak-dev/7893936f-0c5a-4d1b-9b06-de1e5377fbfc%40googlegroups.com.

Stian Thorgersen

unread,
Apr 24, 2020, 8:44:17 AM4/24/20
to Jon Koops, Hamed KARAMOKO, Keycloak Dev
+1 To allow adding custom adapter, which would allow some community maintained extensions as well as fully custom things

Stian Thorgersen

unread,
Apr 24, 2020, 8:46:18 AM4/24/20
to Hamed KARAMOKO, Keycloak Dev
Seems reasonable, but problem I see with it is how to test it. Ideal we'd have some automated testing around different adapters so we could know they work or not. This has always been a bit of an issue with Cordova, but at least we have some knowledge about that in the core team so can manually test it.

Jon Koops

unread,
Apr 24, 2020, 8:46:34 AM4/24/20
to Stian Thorgersen, Hamed KARAMOKO, Keycloak Dev
Can you add it to the backlog Stian? I'm willing to give it a go.

Stian Thorgersen

unread,
Apr 24, 2020, 8:54:24 AM4/24/20
to Jon Koops, Hamed KARAMOKO, Keycloak Dev

Jon Koops

unread,
Jun 14, 2020, 11:03:55 AM6/14/20
to Stian Thorgersen, Stan Silvert, Hamed KARAMOKO, Keycloak Dev
Hi guys, I just wanted to get back to you since I have had some time to take a look at this issue and it turns out that we actually already allow a custom adapter implementation to be passed into the client. However since the type definitions and documentation do not reflect this I can see how this can be missed easily.

@Hamed, I believe the code below should be enough for you to get started to build your own adapter that should work with Capacitor, it would be great if you could find a way to open-source your efforts in this regard to help out others in the future.

import Keycloak, { KeycloakAdapter } from 'keycloak-js';

class MyCustomAdapter implements KeycloakAdapter {
// Implement methods required by KeycloakAdapter here.
}

const keycloak = new Keycloak();

keycloak.init({
adapter: MyCustomAdapter,
});


@Stian Thorgersen @Stan Silvert considering this fact I would propose a couple of changes:

We should provide better type definitions for providing a custom adapter, we apparently already have an interface type for the KeycloakAdapter (see code example above) so I have taken the liberty of actually documenting and using this type more explicitly in this PR: https://github.com/keycloak/keycloak/pull/7168  I'll also try to whip up a PR for the documentation as soon as I have some time.

All things considered I would also recommend that we officially deprecate the cordova and cordova-native options as the examples are not actively maintained and neither of these options is covered by our test suite, meaning they could break at any point in time. After the deprecation it would make sense if we move the native adapter into a seperate repository where it can be tested and better maintained by the community at large.

I'd love to have your opinion on this so let me know what you think.

Regards,
Jon


Stian Thorgersen

unread,
Jun 15, 2020, 6:36:48 AM6/15/20
to Jon Koops, Stan Silvert, Hamed KARAMOKO, Keycloak Dev
On Sun, 14 Jun 2020 at 17:04, Jon Koops <jonk...@gmail.com> wrote:
Hi guys, I just wanted to get back to you since I have had some time to take a look at this issue and it turns out that we actually already allow a custom adapter implementation to be passed into the client. However since the type definitions and documentation do not reflect this I can see how this can be missed easily.

@Hamed, I believe the code below should be enough for you to get started to build your own adapter that should work with Capacitor, it would be great if you could find a way to open-source your efforts in this regard to help out others in the future.

import Keycloak, { KeycloakAdapter } from 'keycloak-js';

class MyCustomAdapter implements KeycloakAdapter {
// Implement methods required by KeycloakAdapter here.
}

const keycloak = new Keycloak();

keycloak.init({
adapter: MyCustomAdapter,
});


@Stian Thorgersen @Stan Silvert considering this fact I would propose a couple of changes:

We should provide better type definitions for providing a custom adapter, we apparently already have an interface type for the KeycloakAdapter (see code example above) so I have taken the liberty of actually documenting and using this type more explicitly in this PR: https://github.com/keycloak/keycloak/pull/7168  I'll also try to whip up a PR for the documentation as soon as I have some time.

Sounds good :)
 

All things considered I would also recommend that we officially deprecate the cordova and cordova-native options as the examples are not actively maintained and neither of these options is covered by our test suite, meaning they could break at any point in time. After the deprecation it would make sense if we move the native adapter into a seperate repository where it can be tested and better maintained by the community at large.

Not sure what's the ideal here. I can see that it would be better to extract the Cordova bits into a separate add-on would be good, and somehow getting to a point where we have some automated testing around it would be great. At the same time I don't necessarily see moving it to a separate repo would result in a higher level of maintenance, perhaps it most likely would actually make the situation worse. Perhaps this is worthy of a separate thread, and also at the same time bring up the idea of extracting keycloak.js into a separate repo, use a more JS friendly testsuite, and also look for people that are interested in joining at a contributor level.

Jo Ba

unread,
Oct 18, 2020, 5:17:31 AM10/18/20
to Keycloak Dev
Hi all,

I want to give a try in this discussion, if there is any chance to add the Capacitor implementation from Hamed Karamoko to the official keycloak.js adapter.
When I checked the option to provide custom adapters I realized that the most of the code in the keycloak.js can be reused, but is not accessible with the custom implementation. (e.g. parseCallback(); processCallback();)

As you can see from Hameds mail, just a few line changes would be needed to implement the Capacitor DeepLink Listener and InAppBrowser.
I would also like to add the capacitor-native adapter. Which is almost the same code from Hamid, but it uses not the InAppBrowser plugin - just window.open()

To implement this as custom adapter would mean a lot of code writing which is already existing in keycloak.js.

Regards,
Johannes

Manuel Bochröder

unread,
Feb 11, 2021, 3:24:59 PM2/11/21
to Keycloak Dev
Hi all,

some guy created a nice out of the box angular-package for this problem: https://github.com/JohannesBauer97/keycloak-ionic/commits/main. Since we don't want to have 3rd Party code in such an critical part of the application, we copied the original keycloak adapter implementation and extended it with the code from keycloak-ionic. This little change would help lot's of people - not just by reducing search time for an solution but also help using the more secure capacitor-native instead of the default mode . I would have created an Issue and PR but i'm not allowed to. 

I would be happy to help you adding this code to the project.

best regards,
Manuel

Reply all
Reply to author
Forward
0 new messages