Authentication

ActiveUI interaction with server security

We will discuss here the general rules that ActiveUI follows with regard to authorization and authentication on the servers it communicates with, followed by its default behavior and finally its behavior in a Cloud environment (with Keycloak).

General principles #

No security client side #

Since ActiveUI runs inside a Web browser it is in debuggable and alterable, so it does not contain any security enforcement mechanisms and nothing private or even sensitive will be stored in it. All the authorization checks are done server side. ActiveUI goal is to provide UI and logic to ask the credentials to the user and use them to authenticate on the servers, nothing more.

Security only when needed #

ActiveUI can also be used to process and display static or public data, so it is not mandatory for a user to authenticate iself when using ActiveUI. This is why the authentication user interface is displayed at the last moment: when a server responds to an HTTP request with a 401 status code.

JWT #

The underlying technology used to safely store credentials in ActiveUI and transfer them to the servers is JWT. It is recommended to get familiar with it thanks to its Wikipedia article and website.

Roles #

The JWT token contains the ordered list of roles the user has. This is used by ActiveUI in some components to decide which roles to assign to created objects. For instance bookmarks and KPIs will use by default the roles of the user in their permissions. This is just for convenience, since the server (ContentServer or ActiveMonitor) will check on its side that the roles of the created content are allowed by the JWT token.

Default behavior #

By default, ActiveUI will use JWT tokens for its authentication with ActivePivot, ActiveMonitor and the Content Server. When trying to communicate with any of these servers, if ActiveUI receives a 401 HTTP response, it will use the first Content Server it knows to generate a JWT token. This token will be stored in the local storage of the browser and sent along every request in the Authorization header.

Sequence diagrams #

The interaction between the different actors of the authentication will be represented with sequence diagrams. In these sequence diagrams the Browser runs ActiveUI code so it includes it as well as the cookies and redirect logic.

First authentication #

The sequence diagram of the first authentication on the first launch is:

participant User as U participant Browser as B participant ActivePivot Server as AP participant ContentServer as CS U->B: Start ActivePivot request B->AP: REST request for discovery AP=>B: 401 No authentication B->U: Prompt for login, password U=>B: Enter login and password B->CS: Ask for JWT token\nwith basic credentials Note over CS: Validate credentials,\nfetch roles CS=>B: JWT token Note over B: Store token in Local Storage B->AP: REST discovery with JWT token Note over AP: Validate token Note over AP: Create session for user AP=>B: Discovery result B->AP: Start WebSocket Note over AP: Use session cookie\nto authenticate WebSocket AP=>B: CellSet over WebSocket

The WebSocket part of the authentication will be described below.

Token refresh #

The token produced by the content server will expire at some point (this is configured in the Content Server), triggering the token refresh process:

participant User as U participant Browser as B participant ActivePivot Server as AP Note over B: Read token from Local Storage B->AP: REST request with JWT token Note over AP: Validate JWT token AP=>B: 401 Token expired Note over B: Delete stored JWT token B->U: Prompt for login, password

The rest of the process is the same than on first connect.

Token sharing #

ActiveUI will use a single JWT token to communicate with all the servers. This is how a user can enter his/her credentials only once and be able to connect to multiple ActivePivot Servers. The constraint on the servers is that all security filters should validate with the public key corresponding to the private key the content server used when generating the token.

Token storage #

ActiveUI will store the token in the local storage, so that when a user closes then reopen the browser the user doesnā€™t need to enter his/her credentials again. It will store along it the username entered by the user so that when the token expires we can already fill the username in the login popup. A malicious browser extension or other person with access to the browser could steal this token, but could then gain access only for the duration of the token.

This duration is controlled by the Content Server, and is recommended to be approximatelly a day or half day of work. A longer duration is not recommended because of the possibility of token theft. A shorter duration is also not recommended because token expiration requires the user to enter his/her credentials again which will be not friendly below the recommended duration.

Password storage #

The password the user types is handled briefly by ActiveUI to create the authentication header (using Basic access authentication) to retrieve the JWT token, after that the password is not kept anywhere so that no extension or other user could steal it.

Cloud security (Keycloak) #

The cloud security relies on Keycloak. Among all features that Keycload includes, the ones important for ActiveUI is that it follows the OAuth2 protocol and uses JWT tokens. ActiveUI interaction with server security is quite different in this situation: ActiveUI is served by a server that has a Keycloak compatible security filter. This means that the user must log in before using ActiveUI. ActiveUI will then work with Keycloak JWT tokens.

First authentication #

participant User as U participant Browser as B participant Content Server as L participant Keycloak Server as K U->B: Open application B->L: Ask for ActiveUI App Note over L: Spring security check Note over L: Not authenticated, redirect L=>B: 302 Redirect to Keycloak Server B->K: Ask for login page K=>B: Login page U->B: Enter credentials B->K: Basic Authentication Note over K: Validate credentials Note over K: Create session\nS1 for user K=>B: 302 Redirect to Content Server with \n Single Usage Token and S1 cookie Note over B: Store S1 cookie for Keycloak server B->L: Request with Single Usage Token Note over L: Keycload Security filter intercepts L->K: Request all tokens \n with Single Usage Token K=>L: Give all tokens Note over L: Store tokens in session S2 L=>B: 302 Redirect to ActiveUI app \n with S2 cookie Note over B: Store S2 cookie for Content Server B->L: Ask for ActiveUI App L=>B: Display ActiveUI App Note over U: Sees ActiveUI App B->L: Ā°Ask for Access Token using S2 cookie L=>B: Ā°Access Token Note over B: Ā°Store Access Token in-memory

The steps starting with a Ā° are implemented in ActiveViam products, either ActiveUI or ActiveViam web components used in ActiveCloud. The other steps are part of Keycloak server and the security filters it provides.

The above process has quite interesting properties:

  • Almost all the steps are pure Keycloak implementation, this process follows the OAuth2 protocol.
  • There are a lot more steps than in the pure JWT implementation, this is because this protocol has more capabilities than a pure JWT based implementation like the default behavior.
  • No ActiveViam component (neither ActiveUI nor ActivePivot nor the Content Server) see the user credentials at some point, they all work only with the JWT token produced by Keycloak.
  • There is not only one token, we saw here a Single Usage Token, an Access Token and there is also a Refresh Token that will be described below.
  • The access token stored by ActiveUI at the end of the process is sufficient to connect to every ActiveViam server with the right roles. This means that all servers that this ActiveUI will connect to should validate in their security filters the JWT access token produced by Keycloak. So they should have the public key corresponding to Keycloak private key in their security configuration.

Next authentication shortly after #

The next time the user opens ActiveUI application while the S2 cookie is available (basically after having closed then reopened the browser, see Timeouts), the following process starts:

participant User as U participant Browser as B participant Content Server as L participant Keycloak Server as K U->B: Open application B->L: Ask for ActiveUI App Note over L: Spring security check Note over L: Session S2 has not expired L=>B: Display ActiveUI App Note over U: Sees ActiveUI App B->L: Ā°Ask for Access Token using S2 cookie L=>B: Ā°Access Token Note over B: Ā°Store Access Token in-memory

The session mechanism of the Content Server is sufficient here, there is no need to communicate with the Keycloak server.

Next authentication #

If the user reopens ActiveUI later (basically the next day, see Timeouts), we will use the Refresh token mechanism of OAuth2. The refresh token is also a token generated by Keycloak and stored on the Content Server (it is quite sensitive so it should never go client side). The goal of this token is to recreate an Access Token from it from time to time. On almost every request to a server the server will quickly check the validity of the token. Once in a while when the access token expires ActiveUI and the Content Server will reach up to the Keycloak Server to check if the user has not been removed in the meantime. The Access Token acts like an authentication cache that can be checked very quickly, the refresh token is more expensive to check but more powerful.

participant User as U participant Browser as B participant Content Server as L participant Keycloak Server as K U->B: Open application B->L: Ask for ActiveUI App Note over L: Spring security check Note over L: Session has expired L=>B: 302 Redirect to Keycloak Server B->K: Ask for login page with S1 cookie Note over K: Recognizes Session, checks \n logs-in automatically K=>B: 302 Redirect to Content Server \n with Single Usage Token B->L: Request with Single Usage Token Note over L: Keycload Security filter intercepts L->K: Request all tokens \n with Single Usage Token K=>L: All tokens Note over L: Store tokens in session S2 L=>B: 302 Redirect to ActiveUI App \n with S2 cookie Note over B: Store S2 cookie for Content Server B->L: Ask for ActiveUI App L=>B: Display ActiveUI App Note over U: Sees ActiveUI App B->L: Ask for Access Token using S2 cookie L=>B: Access Token Note over B: Store Access Token in-memory

Request on Server with expired Access Token #

We describe now what happens when ActiveUI performs an HTTP request on a server like ActivePivot or ActiveMonitor and its Access Token has expired:

participant Browser as B participant Content Server as L participant Keycloak Server as K participant ActivePivot Server as AP B->AP: Ā°REST request with JWT Access Token Note over AP: Ā°Validate JWT Token \n with Keycloak public key AP=>B: 401 Token expired B->L: Ā°Get new Token \n with S2 cookie L->K: Get new Access Token\nwith Refresh Token Note over K: Check Refresh Token, account state K=>L: Access Token Note over L: Store Access Token in session L=>B: Ā°Access Token Note over B: Ā°Store Access Token in-memory B->AP: Ā°Retry request with new Access Token

We see here that in this situation Keycloak is asked to produce a new token and can deny the request if the corresponding account was terminated in the meantime. In the above situation everything goes well, on the other hand if the user has been kicked out we will have the following sequence:

participant User as U participant Browser as B participant Content Server as L participant Keycloak Server as K participant ActivePivot Server as AP Note over U: User logged of by admin U->B: Open application B->AP: Ā°REST request with JWT Access Token Note over AP: Ā°Validate JWT Token \n with Keycloak public key AP=>B: 401 Token expired B->L: Ā°Get new Token \n with S2 cookie L->K: Get new Access Token\nwith Refresh Token Note over K: Check Refresh Token,\n account state K=>L: 401 Refresh Token invalid L=>B: Ā°401 Could not get\n Access Token Note over B: Ā°Start the login again process Note over B: Ā°Clear S2 cookie, Access Token Note over B: Ā°Pause all queries B->L: Ā°Open afterlogin.html Note over L: Spring Security check\n fails: no cookie L=>B: 302 Redirect to\n Keycloak Server B->K: Open Keycloak login page K=>B: Keycloak login page U->B: Enter credentials B->K: Start login flow Note over B: Receives S1, S2 cookies K=>B: 302 Redirect afterlogin.html\n (last denied request) Note over B: Ā°js code in afterlogin.html tells\n opener page login is \n finished and closes itself B->L: Ā°Request for new Access\n Token with S2 L=>B: Ā°New Access Token B->AP: Ā°All queries resume with new Access Token

When the Keycloak server tells ActiveUI (via the Content Server) that the user is no longer connected, ActiveUI will open a pop-up containing the Keycloak login form. Once the user has validated the form ActiveUI will be able to ask again the Content Server its Access Token and resume its operations. During all these steps all HTTP queries are paused and will resume transparently with the new credentials.

Keycloak Timeouts #

A Keycloak authentication flows uses the following authorization holders that have each a different timeout:

Bearer Role Recommended TTL
Access Token Connect to ActivePivot, Sentinel and Content Servers 15 mns
Refresh Token Recreate Access Token when it expired 30 days
Content Server Cookie Find Acces Token 15 mns
Keycloak Cookie Reconnect to Keycloak when Access Token expired to find again Refresh Token 30 days
ActivePivot Server Cookie Bridge between REST and WebSocket security 1 mn

These recommended TTL are the consequence of the following observations:

  • The Access Token can have a short TTL since its refresh does not require any user interaction.
  • The Access Token TTL should not be very short since it still requires multiple HTTP calls and Database reads when it expires.
  • The Access Token should not have a too long TTL otherwise user could continue to use the system for a long time even though administrator banned them.
  • It would be too expensive to ask Keycloak on each call if the Access Token represent a user that was not banned, so the Access Token cannot be revoked.
  • The Refresh Token can be revoked since we ask Keycloak everytime we use it.
  • The Refresh Token never leaves the Servers so it cannot be stolen and can thus have a large TTL.
  • Cookies should have a similar TTL than the token they back otherwise they would maintain sessions with expired tokens.
  • Keycloak invalidates the sessions when a logout happens so it is safe to give a long TTL to Keycloak cookies.
  • The ActivePivot Server Cookie usage is always very short so we donā€™t need to give it a long duration.

Token storage #

When Keycloak is used ActiveUI doesnā€™t even persist the JWT access token it uses (since the cookie is sufficient). The content server stores the Access and Refresh token in its sessions.

Password storage #

When used with Keycloak, ActiveUI never sees any credentials. All the login forms are served by the Keycloak servers via redirects so no ActiveViam component has access to the credentials.

Implementation & Configuration details #

The type of security can be configured through the ActiveUIOptions. For more details on how to configure or create your own security plugin, please refer to the plugin documentation.

WebSockets #

The above documentation was relevant for REST calls, but WeSocket authentication is quite different. This is due to the fact that the WebSocket Client API does not allow ActiveUI to send an Authorization header when opening a WebSocket, which limits greatly our authentication mechanisms.

We can only rely on the cookies to authenticate the WebSockets. So we solve this by performing an HTTP request with a JWT token. This request will create a session on the target server that we will then be able to use for our WebSocket. This gives the following sequence:

participant User as U participant Browser as B participant ActivePivot Server as AP Note over U: Queries ActivePivot B->AP: REST request with \n JWT Access Token Note over AP: Validate JWT Token \n with Keycloak public key AP=>B: Response with cookie S3 Note over B: Store S3 cookie for AP Server B->AP: Open WebSocket with S3 cookie AP=>B: Results Note over U: Sees result