About arc42
arc42, the template for documentation of software and system architecture.
Template Version 8.2 EN. (based upon AsciiDoc version), January 2023
Created, maintained and © by Dr. Peter Hruschka, Dr. Gernot Starke and contributors. See https://arc42.org.
This document describes the Evento Portal, an application that integrates various apps (micro frontends) for school administration in a single web frontend with a common look and feel. The Evento Portal and the integrated apps use the "EVENTO" campus management system by Swiss Learning Hub as their backend.
Goals:
- The Evento Portal and the integrated apps should provide a flexible way to develop new tools in short cycles that can be integrated into the existing "EVENTO" system.
- The user interface should comply with the CI/CD of the Canton of Bern.
- The Evento Portal replaces the former "EventoWeb".
Requirement | Description |
---|---|
Authentication/Login | A user can authenticate via "EVENTO" using the OAuth 2.0 Authorization Code Flow mit Proof Key for Code Exchange (PKCE). |
Logout | A user can logout. |
Token refresh | Access tokens are automatically refreshed so they don't expire. |
Token for apps | The access token is made available in session storage to the apps. |
Language switching | A user can switch the language between German and French. |
Language/Tenant persistence | The selected tenant and language is remembered when logging in the next time with the same browser. |
Base layout | Renders the base layout with header & footer according to the CI/CD of the Canton of Bern. |
Navigation menu | A navigation menu is rendered, providing entry points for modules of the various apps. The navigation items are available depending on the user's roles and permissions. No dynamic configuration is necessary for the definition of the navigation items, structure and permissions. |
App integration | The various apps, implemented with different frontend technologies (such as Angular or Ember) are integrated in a way they don't interfere with each other and are cleaned-up properly when switching apps. |
App routing | The apps' client-side routing is preserved. |
Teacher substitution | Teachers can start & stop substituting other teachers, temporarily gaining the permissions of the substitute. |
User notifications | A user's notifications are displayed (and regularly updated) and can be read/deleted by the user. |
Progressive Web App (PWA) | The Evento Portal is installable as a PWA and offers a simple offline page when no internet connection is available. |
Quality Category | Quality | Description |
---|---|---|
Usability | Operability | Ease-of-use by teachers, students and secretary staff, potentially on mobile devices during a course (responsiveness). |
Usability | Learnability | Teachers, students, and secretary staff should be able to learn to use the application effectively. |
Usability | Accessibility | The application should be accessible in accordance with the accessibility guidelines of the Canton of Bern. |
Compatibility | Co-existence | Various apps implemented with different frontend technologies must be integrated without interference. |
Security | Confidentiality | The Evento Portal should be basically stateless and not persist any user data (particularly data worthy of protection). This task is performed by the "EVENTO" system. |
Quality categories & qualities according to ISO/IEC 25010.
Role | Organisation | Name |
---|---|---|
Specialist Application Manager | BKD-MBA-FBI | @fbufbi |
Technical Application Manager | BKD-MBA-FBI | @schefbi |
Project Leader/SCRUM Master | Puzzle ITC | @mkrebs |
Software Engineer & UX | Puzzle ITC | @caebr |
Software Engineer & Architect | Puzzle ITC | @hupf |
UX Engineer | Puzzle ITC | @shirsbrunner & @pmurkowsky |
Operations | BEDAG | |
Backend Engineering | Swiss Learning Hub |
The Evento Portal shall be...
- ...compatible with evergreen browsers (Chrome, Firefox, Safari, Edge).
- ...published under an open source license.
- ...integrating third-party apps without the possibility to do any modifications to the app's source code.
- ...compliant with the "EVENTO" system's usage of OAuth scopes, where a different access token for a different scope must be used depending on the app.
- ...handling the inability to fetch access tokens asynchronously, therefore incorporate the necessary redirects (with page refresh) without interrupting the user.
graph LR
student(("👩🎓
Student"))
teacher(("👩🏫
Teacher"))
secretary(("👨💼
Secretary"))
admins(("👷
Administrator"))
portal["Evento Portal
Lit Application"]
schulverwaltung["App 'Schulverwaltung'
(Angular)"]
kursausschreibung["App 'Kursausschreibung'
(Ember)"]
etc["More apps..."]
subgraph evento ["EVENTO (SLH)"]
evento-api[REST API]
evento-oauth[OAuth Provider]
evento-gui[Client Application]
end
student --> |uses| portal
teacher --> |uses| portal
secretary --> |uses| portal
secretary --> |uses| evento-gui
admins --> |uses| evento-gui
subgraph portalsystem ["Evento Portal"]
portal <--> |iframe/postMessage/sessionStorage| schulverwaltung
portal <--> |iframe/postMessage/sessionStorage| kursausschreibung
portal <--> |iframe/postMessage/sessionStorage| etc
end
schulverwaltung --> |HTTP/JSON| evento-api
kursausschreibung --> |HTTP/JSON| evento-api
etc --> |HTTP/JSON| evento-api
portal --> |HTTP/JSON| evento-api
portal <--> |OAuth 2.0| evento-oauth
Element | Description |
---|---|
Student (User) | Students of a baccalaureate school or vocational school, reporting and confirming absences, reviewing tests & grades etc. |
Teacher (User) | Class or lesson teachers of a baccalaureate school or vocational school, managing student absences, tests & grades, booking courses etc. |
Secretary (User) | Staff of a baccalaureate school or vocational school office, managing student absences etc. |
Administrator (User) | People configuring EVENTO. |
Apps | Various mini applications – like webapp-schulverwaltung (Angular) or kursausschreibung (Ember) – that provide a frontend for different aspects of the daily school routine. They may consist of multiple modules like "presence control" or "tests" that are individually integrated in the Evento Portal's navigation. |
Evento Portal Lit Application | The evento-portal is the Lit application that implements authentication, the rendering of the base layout with navigation, base routing as well as the integration of the apps via iframe. |
Evento Portal | The Evento Portal as a whole consists of the Evento Portal Lit Application on the one hand, and the compiled artifacts of the various apps, which are part of the repository, on the other. |
EVENTO | The campus management system by Swiss Learning Hub that incorporates a REST/JSON API and an OAuth 2.0 provider. |
- EVENTO API (external documentation)
- EVENTO OAuth 2.0 Provider (external documentation)
- iframe Limitations & Workarounds
- Implement Evento Portal as a client-side rendered application with lightweight Lit/Web Components and minimal dependencies.
- Integrate the apps with an iframe to ensure proper separation of runtime environments.
If no valid access token for the given app/scope (see next section) and no valid refresh token is available, the authentication flow works as follows:
sequenceDiagram
actor user as User
participant portal as Evento Portal
participant app as App
participant oauth as EVENTO OAuth Provider
participant api as EVENTO API
user ->> portal: Visit
activate portal
portal ->> portal: Has valid token? → No
portal ->> portal: Generate random code verifier (CV)
portal ->> portal: Store code verifier in sessionStorage
portal ->> oauth: Redirect to /Authorization/Login w/code verifier
deactivate portal
activate oauth
oauth -->> user: Render login page
deactivate oauth
activate user
user ->> oauth: Choose tenant & login
deactivate user
activate oauth
oauth -->> portal: Redirect back with code
deactivate oauth
activate portal
portal ->> oauth: Request tokens with received code (XHR)
deactivate portal
activate oauth
oauth -->> portal: Return access & refresh token
deactivate oauth
activate portal
portal ->> portal: Store tokens (per scope) in localStorage
portal ->> portal: Store access token for current scope in sessionStorage
portal -->> user: Render base layout
portal ->> api: Request roles & permissions of user
deactivate portal
activate api
api -->> portal: Return roles & permissions
deactivate api
activate portal
portal -->> user: Render navigation
portal ->> app: Load app via iframe
deactivate portal
activate app
app ->> app: Read access token from sessionStorage
app ->> api: Request some data
deactivate app
activate api
api -->> app: Return data
deactivate api
activate app
app -->> user: Render app contents
deactivate app
This flow is implemented with the help of the @badgateway/oauth2-client library in auth.ts. The persistance of the tokens in the browser is realized with token-state.ts. See also the EVENTO documentation page OAuth 2.0 Authorization Code Flow mit Proof Key for Code Exchange (PKCE).
The access tokens are bound to a specific OAuth scope that is specified when getting a token via login page or refresh token endpoint. Each app requires a certain scope since the scope determines what the user can access. Due to this design by the Evento API, it is not possible to acquire and work with a single token for multiple scopes.
The Evento Portal handles tokens & scopes as follows:
sequenceDiagram
actor user as User
participant portal as Evento Portal
participant app as App
participant oauth as EVENTO OAuth Provider
user ->> portal: Visit app with scope A
activate portal
portal ->> portal: Has valid access token for scope A in localStorage? → Yes
portal ->> portal: Store access token for scope A in sessionStorage
portal ->> app: Load app via iframe
deactivate portal
activate app
app ->> app: Read access token from sessionStorage
app -->> user: Render app contents
deactivate app
user ->> portal: Visit app with scope B
activate portal
portal ->> portal: Has valid access token for scope B in localStorage? → No
portal ->> oauth: Redirect to /Authorization/RefreshPublic w/refresh token
deactivate portal
activate oauth
oauth -->> portal: Redirect back with access token for scope B
deactivate oauth
activate portal
portal ->> portal: Store tokens (per scope) in localStorage
portal ->> portal: Store access token for scope B in sessionStorage
portal ->> app: Load app via iframe
deactivate portal
activate app
app ->> app: Read access token from sessionStorage
app -->> user: Render app contents
deactivate app
Apparently, at the time writing this, the Evento API does not provide any way to refresh tokens asynchronously. This means that the tokens can only be renewed with a redirect, interrupting the user. We therefore try to implement the following checks:
- Whenever the scope changes, i.e. the user clicked in the navigation:
- If the refresh token fully expired, redirect to the login page (as described in the "Authentication" flow above).
- If the access token half expired, redirect to the refresh token endpoint to get a new one. Like this, we try to make sure that the token does not expire during usage of the apps (in a potentially unsaved state).
- Start timers to detect when tokens expire:
- If the refresh token expired (timer fires), redirect to the login page (as described in the "Authentication" flow above).
- If the access token expired (time fires), redirect to the refresh token endpoint to get a new one.
The checks on navigation are implemented in auth.ts, the renewal on expiration is implemented in token-renewal.ts.
Here is an example flow of an access token renewal on navigation:
sequenceDiagram
actor user as User
participant portal as Evento Portal
participant oauth as EVENTO OAuth Provider
user ->> portal: Visit or switch app
activate portal
portal ->> portal: Is refresh token expired? → No
portal ->> portal: Is access token more than half expired? → Yes
portal ->> portal: Generate random code verifier (CV)
portal ->> portal: Store code verifier in sessionStorage
portal ->> oauth: Redirect to /Authorization/RefreshPublic w/refresh token
deactivate portal
activate oauth
oauth -->> portal: Redirect back with code
deactivate oauth
activate portal
portal ->> oauth: Request tokens with received code (XHR)
deactivate portal
activate oauth
oauth -->> portal: Return access & refresh token
deactivate oauth
Here is an example flow of a refresh token renewal on expiry (using a timer):
sequenceDiagram
actor user as User
participant portal as Evento Portal
participant oauth as EVENTO OAuth Provider
activate portal
portal ->> portal: For each token, start a timer to fire on expiry
portal ->> portal: Refresh token expiry timer fires
portal ->> portal: Generate random code verifier (CV)
portal ->> portal: Store code verifier in sessionStorage
portal ->> oauth: Redirect to /Authorization/Login w/code verifier
deactivate portal
activate oauth
oauth -->> user: Render login page
deactivate oauth
activate user
user ->> oauth: Choose tenant & login
deactivate user
activate oauth
oauth -->> portal: Redirect back with code
deactivate oauth
activate portal
portal ->> oauth: Request tokens with received code (XHR)
deactivate portal
activate oauth
oauth -->> portal: Return access & refresh token
deactivate oauth
activate portal
portal ->> portal: Store tokens (per scope) in localStorage
portal ->> portal: Store access token for current scope in sessionStorage
portal -->> user: Render portal & app
deactivate portal
The starting or stopping of a teacher substitution also happens via a redirect and results in a new token (without code verifier). The redirect is implemented in the SubstitutionsToggle.ts component, the handling of the result in auth.ts.
graph TD
other["Other app"]
subgraph github-schulverwaltung["GitHub webapp-schulverwaltung"]
schulverwaltung["Repository"]
schulverwaltungAction("build.yml Action")
end
subgraph github-kursausschreibung["GitHub kursausschreibung"]
kursausschreibung["Repository"]
end
subgraph github-portal["GitHub evento-portal"]
portal["Repository"]
pages["GitHub Pages"]
registry["GitHub Image Registry"]
action-netlify("deployTestEnv Action")
action-container("container-build Action")
action-hotfix-container("hotfix-container-build Action")
end
subgraph provider-netlify["Netlify"]
netlify["Evento Portal
development environment
https://evtapp.netlify.app/"]
end
subgraph provider-bedag["Bedag Kubernetes Cluster"]
subgraph container-test["nginx Container"]
test["Evento Portal
test environment
https://evt-test.apps.be.ch/"]
end
subgraph container-production["nginx Container"]
production["Evento Portal
production environment
https://evt.apps.be.ch/"]
end
end
schulverwaltung --> |"on push (main)"| schulverwaltungAction
schulverwaltungAction --> |build & commit| portal
kursausschreibung --> |build & commit manually| portal
other --> |build & commit manually| portal
portal --> |"on push (main)"| action-netlify
action-netlify --> |"build & upload ZIP"| pages
pages <--> |watch & deploy| netlify
portal --> |"manual-dispatch (test, production)"| action-container
portal --> |"manual dispatch (hotfix-*)"| action-hotfix-container
action-container --> |merge to release branches| portal
action-container --> |push & tag Docker image| registry
action-hotfix-container --> |push & tag Docker image| registry
registry <--> |watch & deploy 'test' tag| test
registry <--> |watch & deploy 'production' tag| production
registry <--> |deploy 'hotfix' tag| production
For more details, consider Deployment.
The Evento Portal replaces the EventoWeb and should render a base layout, a statically configured navigation and integrate the various apps. It should also handle the OAuth 2.0 Authentication flow.
- Due to the vague requirements of the targeted operations environment at the time of planning the project, we decided to implement the Evento Portal as a Client-side rendered application.
- Since we have to integrate apps based on different technologies (Angular, Ember), we choose to build a lightweight, framework-agnostic frontend with Lit and Web Components.
Alternatives:
- Server-side rendered web application (e.g. with Node.js or any other backend technology) with full page reloads on navigation.
- The static files we get as build artifacts can be served with any web server and are completely stateless.
- The OAuth 2.0 authentication flow has to be implemented client-side originating from an untrusted client. The refresh & access token must be available to the client and will be stored in the browser.
- Thanks to the shadow DOM Web Components feature any stylesheets on the page will not interfere with styles of components and vice versa.
- Since we have a single page application (SPA), the page will not be reloaded on navigation. Therefore, the JavaScript frameworks of the integrated apps have to be bootstrapped/destroyed cleanly (see ADR 2).
The apps, implemented with different frameworks (Angular, Ember) have to be bootstrapped/destroyed cleanly when switching apps and must not interfere with each other.
- For the safest separation possible, we will integrate the apps via iframe, which is a common approach in micro frontend architectures.
Alternatives:
- Removing DOM nodes via JavaScript, bootstrapping the new app. But what about dangling memory objects, timers/intervals, event handlers and subscriptions?
- The different apps/frameworks have a completely separated runtime environment that is properly cleaned-up, when leaving an app.
- URL Synchronization
- iframe Resizing
- Scrolling
- Positioning of Elements
- Possibly more restrictions...
- Non-standard implementation of OAuth 2.0 by EVENTO.
- Restrictions due to integration of apps via iframe.