OPEN ID support for MapStore - MapStore OPEN ID plugin
Description
This issue is to cover the design of MapStore plugin that will be the interface that MapStore users will use to login, authenticate their credentials and authorize themselves in MapStore. The implementation of this plugin will include Single Sign On (SSO) management.
Single Sign On (SSO) explained Provided I am using a browser to log onto a web application using OPEN ID as authentication protocol and this application is not either MapStore or Geoserver, if I open another browser tab or window of the same same browser and navigate to the MapStore page I won't need to log in as MapStore will recognise a live successful login with OPEN ID from the previously logged application.
Users will find a separate button to allow log in with OPEN ID credentials.
When the users clicks on that button they will be redirected to the Keycloak log screen to insert their OPEN ID credentials, once credentials have been submitted, keycloak will execute its workflow, verifying authentication details with the OPEN ID identity provider.
If user is authenticated, the login process on the front end will complete successfully and the user will access MapStore home page, they will have permissions on resources according to the role given by the OPEN ID identity provider via Keycloak.
The authorization process on Geoserver resources will then follow the present workflow, besed on the authkey token param.
Acceptance criteria
- [ ] Design the button for OPEN ID login in MapStore main login page
- [ ] Develop the redirection process from MapStore to Keycloak
- [ ] Develop the method to manage the Keycloak response (let the user log in if authenticated or prevent log in if not)
- [ ] Develop the SSO capability
Other useful information
@offtherailz, as of 14/06/2022. @taba90 confirmed the development of the BE part for OPEN ID is advanced enough for the FE work to begin.
HOW TO TEST:
Changes to code have not been merged to geostore master yet but are contained in this PR, which source code can be sourced from your local git.
Geostore will have be built in a war with maven see here and after that references to MapStore for the geostore aritfact dependency will have to be changed in case it isn't already to 1.9-SNAPSHOT in the file MapStore2\java\web\pom.xml
Also @offtherailz, as agreed could you please have a start on the SSO (Single Sign On) development requirements this week? And please report in this ticket thread any findings you will find, especially if unexpected issues may arise.
As of 17/06/2022
In Single Sign On, when a user logs in, using Keycloak in a third party application, it receives a token, stored in the borwser cookies. @offtherailz raised the concern that the retention of such cookie could be problematic if we intend to log in onto MapStore and we got this token from a third party application.
@offtherailz @taba90 have been working on a fix on Geostore that could alleviate potential problems of using cross domains cookies with this PR
within SSO, if the process of cross domain cookies usage is painless, the user will submit the token to MapStore which will in turn submit it to the Keycloak service on the BE side, token validation will then take place and if token is valid the user will be automatically logged in.
Some questions are still do be clarified at this stage, assuming SSO is going to work painlessly, namely
- if the call to Keycloak service to validate token is done automatically by the Keycloak Be service
- Which entry point from FE to BE we can use to submit the cookie with the authentication token, options assumed were the same MapStore login entry point or probably an additional entrypoint needs designing. @offtherailz @taba90 to clarify the above points and report to @ale-cristofori
Hi @ale-cristofori, I need to correct a part of your report : About the cookie from 3rd party, I was saying that when you already logged in on a 3rd party application, you may need anyway to click at least to the login button in order to login on MapStore too (via keycloak, without inserting password if you already logged in from another applications) because security policies of the browser do not allow to communicate the session presence between application on different domains (via cookies or other techniques).
Some SSO systems that allow a sort "zero-click" SSO (when you logged in in another application, you are automatically logged in in all the application on the same site/domain, without any other interaction). The applications of this kind work typically on the same domain.
but I think this is not required. Isn't it?
About SSO support. After a long investigation we found that in order to find out if you are logged in in another appliation you must :
- Configure keycloak and MapStore:
- create a public client under the same realm, let's call it
mapstore-client(different then the one ofmapstore-server) - initialize the keycloak.js API with the JSON of the client
mapstore-clientconfigured before, and provided by keycloak admin console.
- create a public client under the same realm, let's call it
- At runtime, mapstore, configured, must (this have to be implemented):
- Load thekeycloak javascript API from the server (http://localhost:8080/js/keycloak.js or whatever, the URL can be passed in configuration of MapStore).
- intercept login / logout events from JS API (in an epic for instance)
- On login event from JS API, make the user logged in on MapStore
- On logout event from JS API, make the user logged out in MapStore
for the login: After this the JS API intercept login, you have the a token and a refreshToken that can be used by MapStore and the backend is able to recognize the user. In order to activate the standard mapstore login we should try to reuse these tokens. We need to try to call a refresh token API of GeoStore or some other entry point to make the back-end aware of the token and initiate the stardard workflow. This need to be tried yet during development.
On logout events (that should be intercepted by the keycloak JS API, we should force the logout even on the client.
here a sample page of demo of the API (also login / logout button for testing).
<!DOCTYPE html>
<html>
<head>
<style>
body {background: #333;}
.log-boolean,
.log-undefined {color: magenta;}
.log-object,
.log-string {color: orange;}
.log-number {color: cyan;}
</style>
<script src="http://localhost:8080/js/keycloak.js"></script>
<script>
function initKeycloak() {
const keycloak = new Keycloak();
keycloak.init({
"realm": "master",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "external",
"clientId": "mapstore-cl",
"public-client": true,
"confidential-port": 0,
onLoad: 'check-sso'
})
keycloak.onAuthSuccess = (e) => {
console.log(">>>>> Login >>>>>");
console.log(`Returned value ${e}`);
console.log(`Token : ${keycloak.token}`);
document.getElementById("loginbtn").innerHTML="LOGOUT"
};
keycloak.onAuthLogout = (e) => {
console.log("xxxxx Logout xxxxxxx ")
console.log(`Returned value ${e}`);
console.log(`Token : ${keycloak.token}`);
document.getElementById("loginbtn").innerHTML="Login"
};
login = () => keycloak.authenticated ? keycloak.logout() : keycloak.login();
}
</script>
</head>
<body onload="initKeycloak()">
<pre id="logger"></pre>
<!-- your page content goes here -->
<script>
(function (logger) {
console.old = console.log;
console.log = function () {
var output = "", arg, i;
for (i = 0; i < arguments.length; i++) {
arg = arguments[i];
output += "<span class=\"log-" + (typeof arg) + "\">";
if (
typeof arg === "object" &&
typeof JSON === "object" &&
typeof JSON.stringify === "function"
) {
output += JSON.stringify(arg);
} else {
output += arg;
}
output += "</span> ";
}
logger.innerHTML += output + "<br>";
console.old.apply(undefined, arguments);
};
})(document.getElementById("logger"));
// Testing
console.log("Hi!", {a:3, b:6}, 42, true);
console.log("Multiple", "arguments", "here");
console.log(null, undefined);
console.old("Eyy, that's the old and boring one.");
</script>
<button id="loginbtn" style="position: absolute; top: 0; right: 0" onClick="login()">Login</button>
</body>
</html>
Implementation Notes and Hints
- The keycloak API intercepts only login on startup (page load) and logout event live. To be logged again after an external login, you may need to refresh the page, while logout even is effectively intercepted automatically.
- We need to handle the case when you have a previous token in localStorage and the JS api provides other tokens.
- The
mapstore-clientkeycloak client you must be set the origin of MapStore client (e.g. http://localhost:8081, or the final server) in order to secure the JS API. - JS of keycloak API must be loaded dynamically from the keycloak server.
- in the authenticationProviders we should allow to insert and entry like:
{
"type": "openid",
"provider": "keycloak",
"sso": {
"type": "keycloak",
"config": " <JSON provided by keycloak>",
"jsURL": "http://localhost:8080/js/keycloak.js"
}
}
where:
- sso: is the new entry that have to be parsed on startup to load keycloak.js api and intialize the keycloak flow
- jsURL: url where to load the keycloak JS API.
- config: is the JSON configureation provided by keycloak, to init the JS Object
Some SSO systems that allow a sort "zero-click" SSO (when you logged in in another application, you are automatically logged in in all the application on the same site/domain, without any other interaction). The applications of this kind work typically on the same domain.
but I think this is not required. Isn't it?
@offtherailz ~~No, the described beahviour (no click login within SSO) was not specified on the ACs so it is not required~~
I named it no click SSO to clarify the exact behaviour, but it seems to be present:
the user is already authenticated (active session from another application) and he is automatically logged into Mapstore
As of 04/07/2022 state of the art:
- Test environment is ready to accept a deployed instance of MapStore with OPENID and Keycloak integration (SSO still needs a bit more of work). This revert PR https://github.com/geosolutions-it/MapStore2/pull/8380 needs merging first and then master is good to be deployed
- Roles management in Geostore is currently still in a PR ready to be merged (@offtherailz and @taba90 to test this and merge, preferably when SSO work is completed) https://github.com/geosolutions-it/geostore/pull/288
@randomorder AS FOR MapStore with OPENID and Keycloak integration (pending SSO support) - deploy the instance preferably by the end of today
NOTES:
Once this PR is merged https://github.com/geosolutions-it/MapStore2/pull/8380, the changes will be built on master docker image, the one you are going to use for the deployment, you can follow/test the docs @offtherailz put together to set up Keycloak for the instance https://mapstore.readthedocs.io/en/latest/developer-guide/integrations/users/openId/#keycloak
@randomorder please let me know down here in the comments if more info is required to start the build/deploy of master on the geoFIT VM. https://github.com/geosolutions-it/DevOps/issues/988
@tdipisa, with @offtherailz and @taba90, we were reflecting how to test this in dev using the master branch. At least we could test the Google OPEN ID integration. @offtherailz suggested we could restrict the access to MapStore to emails in the geosolutionsgroup.com domains.
Taking a look at the docs, and SO there could be a method we could use to achieve the purpose said above, just for QA internal testing purposes, and it would be to use the hd (hosted domain) parameter in the OAuth request.
https://developers.google.com/identity/protocols/oauth2/openid-connect#hd-param
https://stackoverflow.com/questions/10858813/restrict-login-email-with-google-oauth2-0-to-specific-domain-name
Not sure if this is method is relevant to our specific case, in any case this would require some more work and investigation @tdipisa. Do you think it would be worth to add OPENID/Google support on MapStore dev?
@ale-cristofori of course, yes. We need to have all features we release properly tested in DEV and QA before a delivery. I agree to restrict the possibility to login with google only to our internal users, if possible, or at least enable this new feature only temporarily to allow the definition and execution of functional tests for the future maintenance. Let's discuss then if keep it always enabled at least for the release instance. So summarizing I would possibly like to discuss the points below:
- Make that new feature working in DEV only for our domain
- Have it enabled and working for everybody on the release instance
As of today 04/08/2022, the testing of this feature on our dev environment is dependent on time constraints dictated by our resources availability, estimation on the setup of the testing environment has been given by @offtherailz, we are now waiting for any free MapStore resource who can work on this.
see https://github.com/geosolutions-it/MapStore2/issues/8429
The OPEN ID support has been tested with #8434