Keycloak with chrome extension

I am trying to use Keycloak for authorization in chrome extension.
I managed to get the authorization data after entering the login and password in the popup, but the problem is that after returning with this data back to the extension, the keiklock does not know what to do.

I looked at the source code of the init method, it refers to location.href to collect and set authorization data, but in extension it works differently, since it is displayed in an iframe and we do not have the usual way of working with location.href. Keycloak does not know how to handle this situation automatically.
As a result, I have all the data for the keylock, but I cannot transfer it to him, because the api does not imply such a possibility, i.e. bypassing the automatic reading of this data from the address line

Please tell me how to solve this problem?

Hi @luchnikovs is it correct that you build your first key cloak “app” as a chrome extension? If so, I’d recommend that you first build a simple as possible browser app and learn about the authentication flows.
The next question - I guess you want a user to log into key cloak so that the extension gets a token and present it to some other server / service? If that’s correct, you are building a public client (typically a SPA) and need the related flows. Use https://infosec.mozilla.org/guidelines/iam/openid_connect.html as a first reference.
Again, if you try building an SPA first, using a public client config in Keycloak (there must not be a client secret) you will find much more reference.
If that’s done, the behavior in a chrome extension should be much more clear.

I’m sorry in case you know all that and just fight with the browser extension, then this is not helpful :slight_smile:

Thanks for your responce.

I think I gave too few details, sorry.

We have two projects, one is a classic SPA, a site hosted on a public domain, and a chrome extension published in the extension store.
Until recently, I used google oauth authorization without using any third-party libraries. That is, I manually formed the request url with the corresponding parameters client_id, redirect_url, scope, etc. This worked great in both projects.

But recently we were faced with the task of enabling the user to choose the appropriate authorization service (Google, Facebook …) and we decided to abandon the current implementation in favor of a specialized solution with simpler scaling and customization. The choice fell on Keycloak.
There were no problems with setting up Keycloak on the site, it works fine now, but we ran into difficulties with the extension. After passing the authorization form, we receive redirect_url - https: // <extension_id> .chromiumapp.org / # state = 56953c2b-accc-4ebf-891a-1a6d946df468 & session_state = c96717f5-9f8e-4691-a45e-1c283-acec18095adb4 & code27 ad9b-4af34352df6b.c96717f5-9f8e-4691-a45e-1c283c183adb.df045d01-abdc-49f5-820d-5f6c584ab41a and go back to the extension. And at this point we have a problem - I have no way to send this data to Keycloak, since the keyclock.init method reads this data from location.href, and in our case we are in an iframe that has an unchangeable location.href equal to chrome-extension: // <extension_id> / popup.html.

How can I force data to Keycloak that I already received in redirect_url?

This sounds as if it would be worth to subclass the class with the wrong initial, build a better init and enable the subclass as default implementation :slight_smile:

I use keycloak too to authenticate user on my chrome extension.
I use laucnhWebAuthFlow() function to authenticate on keycloak login template and it works fine.
With this i don’t use keycloak object and methods.

Thx for the info. Which response type did you use? Are you running anything else in the callback?

I did the same. Please tell me if you are experiencing problems with saving the session? Doesn’t it log you out after exiting the browser or restarting your computer?

No, i didn’t experienced problems cause session is managed by cookies created by keycloak. The only problem i encountered is about Chrome browser. His cookies management is particular and i needed to overload launchWebAuthFlow() function to grant SSO to work between my apps on that browser (not the case with Mozilla and Opera)

Сould you give me an example of your code, please? I would like to understand what kind of request uris you use, with what parameters openId, how you update the token and how you get the session id.

Here is my login function :

async function login() {
  const redirectURL = browser.identity.getRedirectURL();
  const clientID = "******";
  const scopes = "openid";

  let loginURL = `${url_auth}/auth`;
  loginURL += `?client_id=${clientID}`;
  loginURL += `&response_type=code`;
  loginURL += "&state = default";
  loginURL += `&redirect_uri=${encodeURIComponent(redirectURL)}`;
  loginURL += `&scope=${encodeURIComponent(scopes)}`;
  loginURL += `&prompt=select_account`;
  let url = {};
  try {
    url = await launchWebAuthFlow({
        interactive: true,
        url: loginURL,
        redirect_uri: redirectURL,
        alwaysUseTab: false
      })
  } catch (err) {
    alert(url + 'Erreur rencontrée. ' +
          '\nNom de l\'erreur : ' + err.name +
          '\nMessage d\'erreur : ' + err.message +
          '\nEmplacement de l\'erreur : ' + err.stack);
    await update_menus(false);
    throw new Error(chrome.i18n.getMessage("erreur_reseau"));
  }
  let hash;
  let JsonUrl = {};
  let hashes = url.slice(url.indexOf('?') + 1).split('&');
  for (let i = 0; i < hashes.length; i++) {
      hash = hashes[i].split('=');
      JsonUrl[hash[0]] = hash[1]; 
  }
  const code = JsonUrl.code;
  let details = {
    "redirect_uri" : redirectURL,
    "client_id": "******",
    "grant_type": "authorization_code",
    "code": code,
    "scope": "openid",
  };
  
  let detailsBody = [];
  for (let property in details) {
    let encodedKey = encodeURIComponent(property);
    let encodedValue = encodeURIComponent(details[property]);
    detailsBody.push(encodedKey + "=" + encodedValue);
  }
  detailsBody = detailsBody.join("&");
  let user = {};
  user = await fetch(`${url_auth}/token`, {
    method: 'POST',
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: detailsBody
  })
  .then(response => response.json())
  .then(response => {
    let userDetail = JSON.parse(atob(response.access_token.split('.')[1]));
    // Il faut decoder le prénom de l'utilisateur pour gérer les accents
    let user_firstname = userDetail.given_name;
    let fixed_firstname = decodeURIComponent(escape(user_firstname));
    user["first_name"] = fixed_firstname;
    user["refresh_token"] = response.refresh_token;
    user["access_token"] = response.access_token;
    return user;
  });
  await browser.storage.local.set({
    refresh_token: user.refresh_token,
    access_token: user.access_token,
    first_name: user.first_name,
  });
  await update_menus(true);
  return user;
}

And here is my auth url :
let url_auth = "https://auth.[domain]/auth/realms/[real_name]/protocol/openid-connect";

1 Like