This document breaks down WiMetrix's application architecture. We will describe the pieces that make up our architecture. We will also detail how these pieces fit in together to create make sure our web applications are fast, secure, and reliable.
- ApiSix: Api gateway, Ingress controller
- KeyCloak: Authentication and Authorization provider
- REST Services: Microservice based REST API network
- Frontend Applications: React based Single Page Applications (SPA)
- Mobile Apps: React native based mobile and tablet applications
- Desktop Applications: Electron based desktop applications written with web technologies
Apache ApiSix is being used as an API Gateway.
ApiSix provides rich traffic management features like:
- Load Balancing
- Rate Limiting
- Authentication
- Authorization
- Logging
- Dynamic Upstream
- Circuit Breaking
- Observability
- Canary Release
ApiSix offers these features by integrating with popular 3rd party libraries. This modular design enables us to integrate existing tools and workflows into ApiSix with ease.
ApiSix can be configured through a REST API, and it also comes with a web-based dashboard.
- Enables a singular pathway for cross application communication (frontend-to-backend, backend-to-backend)
- Exposes our backend services and frontend applications over the network securely
- Secure backend service calls through an Auth Provider (See: KeyCloak)
- Enable zero config load balancing
- Provide simple setups for logging, monitoring, and observability
TODO
Example Route config:
uri: /pack-and-ship/*
name: packing-shipping-backend
plugins:
authz-keycloak:
_meta:
disable: false
client_id: apisix
client_secret: ${{CLIENT_SECRET}}
discovery: http://${{KEYCLOAK_IP:PORT}}/realms/wimetrix/.well-known/uma2-configuration
lazy_load_paths: true
cors:
_meta:
disable: false
allow_credential: false
allow_headers: "*"
allow_methods: "*"
allow_origins: "*"
expose_headers: "*"
max_age: 5
proxy-rewrite:
regex_uri:
- ^/pack-and-ship/(.*)$
- /$1
serverless-pre-function:
functions:
- >-
return function() local core = require "apisix.core"; local token =
"test"; if not token then ngx.status = 500; ngx.say('{ "error":
"env_missing", "error_description": "env APISIX_TOKEN not found" }');
ngx.exit(ngx.HTTP_OK); end core.request.set_header("x-apisix-token",
token); end
phase: before_proxy
upstream:
nodes:
- host: 10.0.0.42
port: 5006
weight: 1
type: roundrobin
status: 1KeyCloak is an Identity and Access Management solution. It is based on open standards like OpenID Connect, UMA, OAuth 2.0, and SAML.
KeyCloak provides:
- Single-Sign On
- User federation
- Strong authentication
- User management
- Fine-grained authorization
- Integrations with 3rd party auth/identity providers
- Social Login
KeyCloak can be managed through an Admin UI, as well as through a REST API.
KeyCloak handles every aspect of user authentication and authorization in our architecture. All applications authenticate directly or indirectly through KeyCloak.
Frontend applications authenticate directly with KeyCloak through Openid-compliant REST API interfaces.
- Users login with a familiar username/password method from the login page
- Upon successful authentication, The application receives an
Access Token, aRefresh Token, andUser Infofor the authenticated user Access Tokenhas a short default expiry time of 3 minutesRefresh Tokenhas a default expiry time of 30 minutes- When
Access Tokenexpires, the application uses theRefresh Tokento obtain a new one Refresh Tokenexpiry reset with every refresh, allowing the user to stay logged-in when they are actively working on the frontend- The user is logged out after
Refresh Tokenexpires (which happens after user is inactive for the duration of the token's expiry period) - Active user sessions are visible and can be revoked from the KeyCloak admin dashboard at any time
Access Tokenis forwarded in the Authorization header as a bearer token with every call to the backend services- The API Gateway redirects the request to
KeyCloakto be authorized - The request is forwarded only if the token is valid and the user is authorized to access the route
- ApiSix which sits between the user/client and the services and redirects to KeyCloak for auth
- Backend services are only accessible through ApiSix
KeyCloak is an unopinionated and general purpose tool and allows many different general purpose entities. These entities can be mixed and matched to fit specific organizational structures.
Here's our recommended setup:
Use Role-Based Access Control (RBAC) by combining Roles, Groups, and Users.
Role: A specific and narrow permission. Usually maps with an action to be performed.- Examples: (
get production order,lock packing list,delete pack jobs,approve packing list) - It is possible to create composite roles that combine several other roles into a super role
- Examples: (
Group: A user type or an organization role.- As the name suggest, groups combine many permissions(
Roles) under a single name - Assign one or many
Rolesto eachGroup
- As the name suggest, groups combine many permissions(
User: A system user. Users can be assigned one or moreGroups.- A user with an assigned groups inherits all the
Rolesassigned to the group Rolescan also be mapped toUsersdirectly for fine-grained control.- However it should be avoided if possible to make access control flow simple to follow/understand.
- A user with an assigned groups inherits all the
TODO Add screenshots
Read here for a detailed breakdown of KeyCloak
- Deploy the KeyCloak container
- Access the admin UI (the default port is
8080) - Create a realm called
wimetrix. We will use them realm for our auth - Setup
Users,Groups, andRealm Roles. (See: User Access Management) - Minimum and Maximum
Refresh Tokenage can be changed fromRealm settings > Sessions > SSO Session Settings - Minimum and Maximum
Access Tokenage can be changed fromRealm Settings > Tokens > Access Tokens - Go to
Realm Settings > Token > Refresh Tokens- Set
Revoke Refresh Tokentotrue - Set
Refresh Token Max Reuseto1
- Set
- Add a client scope called
groups, to return the user's groups in user info routes- Type should stay
none - Set
Display on consent screentofalse - Set
Include in token scopetofalse - Add a
Group Membershipmapper calledgroups(it is not present in predefined mappers) - Add to Token claim
groups - Add to
user infoandtoken introspection
- Type should stay
- Add a
Client Scopecalleduser_id, to return the user's id in user info routes- Type should stay
none - Set
Display on consent screentofalse - Set
Include in token scopetofalse - Add a
User Propertymapper (it is not present in predefined mappers) - Set
user_idas the value forNameandToken Claim Name - Set
idas the value forProperty - Set
Full group pathtofalse - Add to
access token,user infoandtoken introspection
- Type should stay
- Go to the
Client Scopecalledroles- Open the
realm rolesmapper- Set
Add to access tokentotrue - Set
Add to userinfototrue
- Set
- Open the
client rolesmapper- Set
Add to access tokentotrue - Set
Add to token introspectiontotrue
- Set
- Open the
- Create A client for the frontend applications
- Set
Client IDandNametofrontend - Make sure
Client AuthenticationandAuthorizationStay off - Keep
Standard flowandDirect access grantschecked inAuthentication flow - Set
Valid redirect URIsto your frontend url (Set/*in dev) - Set
Web originsto your frontend url (Set*in dev) - In
Client scopes, adduser_idandgroupswith assigned typeDefault
- Set
- Create A client for ApiSix
- Set
Client IDandNametoapisix - Set
Client AuthenticationandAuthorizationtotrue - Uncheck everything in
Authentication flow - In
Client scopes, adduser_idandgroupswith assigned typeDefault - Make
- Setup Authorization by going to the
Authorizationtab- In the settings tab:
Policy Enforcementshould beEnforcingDecision Strategyshould beUnanimousRemote Resource Managementshould beoff
- In the
Resourcestab- Delete
Default Resource - Add Resources for all routes uris
- Delete
- In the
Policiestab- Define all access policies
- Prefer Using
Roletype policies
- In
Permissionstab- Map all
Resourcesto one or morePolicies
- Map all
- In the settings tab:
- Set
- Go to
Authentication>Required actionsand uncheck all required actions
This Postman collection
can be used to test the auth flow (Make sure to update the Variables like url and client_secret for the collection)
The Backend applications are divided into many independent REST microservices. Each microservice has a narrow scope and can be independently deployed.
The backend services use the following tech stack:
- JavaScript: Our backend programming language of choice
- TypeScript: Type-safe JavaScript
- Node.js: JavaScript Runtime for the backend services
- Fastify: Node.js framework for the backend services
Frontend Web applications are written in react as Single Page Applications. They access the server/db through the backend services. The frontend is decoupled from the server side logic and can be deployed independently of the backend services.
The frontend applications use the following tech stack:
- JavaScript: The programming language of the web
- TypeScript: Type-safe JavaScript
- React: Frontend library for JavaScript
- Tanstack Router: Routing framework
- Material UI: Component Library and Design System
- Apache ECharts: Chart and Visualization components
- Material React Table: Data Grid and table component and hook
- Vite: Dev Server and Bundler
Mobile and Tablet applications are developed in React Native. This enables easy cross-platform availability and code reuse with the web frontend. It also allows us to streamline our tech stack and unify on a JavaScript/TypeScript Stack. This also enables us to integrate the mobile applications tightly into the monorepo architecture.
The apps use the following tech stack:
- JavaScript: The programming language of the web
- TypeScript: Type-safe JavaScript
- React: Frontend library for JavaScript
- React Native: Native components and JS-Native bridge to translate javascript into native code
- Expo: React Native meta-framework providing builtin workflows and libraries
- React Native Paper: Component Library and Design System
We aim to avoid desktop applications, preferring instead to go with web applications. For use-cases where a desktop applications are required, we develop them with Electron.js. This enables us to use web technologies to build the application, which keeps our tech stack unified.
The desktop applications use the following tech stack:
- Uses the frontend tech stack. (See: Frontend Applications)
- Electron: Cross-platform desktop apps with web technologies
- Electron Forge: Tool to scaffold, build, and publish electron applications
