Compare commits

...

7 Commits

Author SHA1 Message Date
Ken Sternberg
13e79b4793 Revised README as requested by @tanberry 2026-04-29 13:39:54 -07:00
Ken Sternberg
4f16bed6a5 Just a few updates. 2026-04-29 10:35:46 -07:00
Ken Sternberg
8fb3569333 Added architecture documentation. 2026-04-29 09:43:34 -07:00
Ken Sternberg
471493a118 Merge branch 'main' into web/docs/updated-readme
* main: (782 commits)
  web: bump knip from 6.4.1 to 6.6.0 in /web (#21957)
  core: bump github.com/getsentry/sentry-go from 0.45.1 to 0.46.0 (#21955)
  core: bump uvicorn[standard] from 0.44.0 to 0.45.0 (#21956)
  core: bump rustls from 0.23.39 to 0.23.40 (#21958)
  core: support hashed password in users API + automated install (#18686)
  core, web: update translations (#21952)
  providers/saml: generate issuer url when provider is set on app (#18022)
  root: fix rust build with uv-installed Python (#21858)
  core: add support for hiding applications from the user dashboard (#21530)
  core: bump ruff from 0.15.11 to 0.15.12 (#21871)
  packages/ak-axum/router: add X-Powered-By to all responses (#21940)
  core: bump microsoft-kiota-serialization-form from 1.9.8 to v1.10.1 (#21909)
  core: bump pytest-randomly from 4.0.1 to 4.1.0 (#21873)
  core: users/groups reduce number of database queries (#20431)
  core: bump types-channels from 4.3.0.20260408 to 4.3.0.20260421 (#21872)
  ci: bump taiki-e/install-action from 2.75.21 to 2.75.22 in /.github/actions/setup (#21877)
  core, web: update translations (#21870)
  sources/oauth: ensure user ID is returned as str (#21880)
  translate: Updates for project authentik and language no_NO (#21862)
  core: bump maxminddb from 3.0.0 to v3.1.1 (#21907)
  ...
2026-04-29 09:35:21 -07:00
Ken Sternberg
8c27b7db26 Spelling errors will be the death of me. 2026-03-06 18:52:52 -08:00
Ken Sternberg
cea8f1624a Prettier, naturally, has opinions. 2026-03-06 18:49:58 -08:00
Ken Sternberg
d48c3382dd web: revised the README to be more of an onboarding document 2026-03-06 18:48:37 -08:00
7 changed files with 442 additions and 89 deletions

View File

@@ -1,112 +1,303 @@
# authentik WebUI
# The authentik WebUI
This is the default UI for the authentik server. The documentation is going to be a little sparse
for awhile, but at least let's get started.
The authetik WebUI is the default UI for the authentik Single Sign-on (SSO) server. It consists of
three primary applications:
# The Theory of the authentik UI
- Flow: The transaction-driven, customizable interface for logging in and all other workflow
activities
- User: The user's library of applications to which they have access, and user settings such as
configuring their MFA and email address, viewing their current sessions, and more
- Admin: The system administration tool for defining applications, providers, policies, and
everything else
In Peter Naur's 1985 essay [Programming as Theory
Building](https://pages.cs.wisc.edu/~remzi/Naur.pdf), programming is described as creating a mental
model of how a program _should_ run, then writing the code to test if the program _can_ run that
way.
Each of these is a [thin client] application around data objects provided by and transactions
available with the authentik SSO server. Business logic and validation is provided by the server.
The mental model for the authentik UI is straightforward. There are five "applications" within the
UI, each with its own base URL, router, and responsibilities, and each application needs as many as
three contexts in which to run.
The authentik SSO server is written in Python and Django.
The three contexts corresponds to objects in the API's `model` section, so let's use those names.
> - [thin client](https://en.wikipedia.org/wiki/Thin_client): In this case, we mean "a front end to
> show the data where the server does all the heavy lifting."
- The root `Config`. The root configuration object of the server, containing mostly caching and
error reporting information. This is misleading, however; the `Config` object contains some user
information, specifically a list of permissions the current user (or "no user") has.
- The root `CurrentTenant`. This describes the `Brand` information UIs should use, such as themes,
logos, favicon, and specific default flows for logging in, logging out, and recovering a user
password.
- The current `SessionUser`, the person logged in: username, display name, and various states.
(Note: the authentik server permits administrators to "impersonate" any other user in order to
debug their authentication experience. If impersonation is active, the `user` field reflects that
user, but it also includes a field, `original`, with the administrator's information.)
## Project setup
(There is a fourth context object, Version, but its use is limited to displaying version information
and checking for upgrades. Just be aware that you will see it, but you will probably never interact
with it.)
If you have cloned the authentik repository, the [developer docs] are where you go to perform the
initial set-up and install. This sequence will get you up and running in the usual course of events.
There are five applications. Two (`loading` and `api-browser`) are trivial applications whose
insides are provided by third-party libraries (Patternfly and Rapidoc, respectively). The other
three are actual applications. The descriptions below are wholly from the view of the user's
experience:
```
$ make install
$ make gen-dev-config
$ make migrate
$ make run-server
$ make run-worker
```
- `Flow`: From a given URL, displays a form that requests information from the user to accomplish a
task. Some tasks require the user to be logged in, but many (such as logging in itself!)
obviously do not.
- `User`: Provides the user with access to the applications they can access, plus a few user
settings.
- `Admin`: Provides someone with super-user permissions access to the administrative functions of
the authentik server.
We recommend that you run `run-server` and `run-worker` in different terminals or different sessions
under tmux or screen or a similar terminal multiplexer.
**Mental Model**
The WebUI runs in this folder (`./web` under the project root). You can put the WebUI into hot
reload by running, from this folder,
- Upon initialization, _every_ authentik UI application fetches `Config` and `CurrentTenant`. `User`
and `Admin` will also attempt to load the `SessionUser`; if there is none, the user is kicked out
to the `Flow` for logging into authentik itself.
- `Config`, `CurrentTenant`, and `SessionUser`, are provided by the `@goauthentik/api` application,
not by the codebase under `./web`. (Where you are now).
- `Flow`, `User`, and `Admin` are all called `Interfaces` and are found in
`./web/src/flow/FlowInterface`, `./web/src/user/UserInterface`, `./web/src/admin/AdminInterface`,
respectively.
```
$ npm run watch
```
Inside each of these you will find, in a hierarchal order:
## Front-End Architecture
- The context layer described above
- A theme managing layer
- The orchestration layer:
- web socket handler for server-generated events
- The router
- Individual routes for each vertical slice and its relationship to other objects:
### The Django side
Each slice corresponds to an object table on the server, and each slice _usually_ consists of the
following:
The authentik web-based applications are delivered via a Django server. The server delivers an HTML
template that executes a sequence of startup operations:
- A paginated collection display, usually using the `Table` foundation (found in
`./web/src/elements/Table`)
- The ability to view an individual object from the collection, which you may be able to:
- Edit
- Delete
- A form for creating a new object
- Tabs showing that object's relationship to other objects
- Interactive elements for changing or deleting those relationships, or creating new ones.
- The ability to create new objects with which to have that relationship, if they're not part of
the core objects (such as User->MFA authenticator apps, since the latter is not a "core" object
and has no tab of its own).
- Assigns the language code and `data-theme="light|dark"` settings to the `html` tag. Assigns the
favicon links. Creates a `window.authentik` object and assigns a variety of site-wide
configuration details to it. Probes the a priority decision list of sources of the user's
light/dark preferences and assigns that to the `Document.dataset`
- Begins loading the interface root bundle. The script is of `type="module"`; it will not be
executed until the initial HTML has been completely parsed.
- Loads the site-wide standard CSS.
- Injects any CSS overrides specified in the customer's `brand` settings
- Loads any necessary JavaScript polyfills
- Dispatches any initial messages to the notification handler
- Sets any custom `<meta>` settings specified by the server
- Provides the initial HTML scaffolding to launch an interface.
We are still a bit "all over the place" with respect to sub-units and common units; there are
folders `common`, `elements`, and `components`, and ideally they would be:
The interface code is mostly the core web component and its responsibilities. This code will be
hydrated when the interface root bundle in the second step above is executed and the components are
registered with the browser.
- `common`: non-UI related libraries all of our applications need
- `elements`: UI elements shared among multiple applications that do not need context
- `components`: UI elements shared among multiple that use one or more context
### The Flow interface
... but at the moment there are some context-sensitive elements, and some UI-related stuff in
`common`.
The Flow interface has three subsystems:
# Comments
- The Locale Selector: `<ak-locale-select>`
- The Flow Inspector: `<ak-flow-inspector>`
- The Flow Executor: `<ak-flow-executor>`
**NOTE:** The comments in this section are for specific changes to this repository that cannot be
reliably documented any other way. For the most part, they contain comments related to custom
settings in JSON files, which do not support comments.
The Flow interface is a single-page application. The Locale Selector and the Inspector are
independent buttons that persist on the web page for the duration of a Flow. Either can be disabled
and hidden by admin preference. The Locale Selector allows the user to select an alternative locale
in which to display text and labels. The [Flow
Inspector](https://docs.goauthentik.io/add-secure-apps/flows-stages/flow/inspector/), when enabled,
can query the server and then display details about the state of a flow: the accumulated context of
the current flow, existing error messages, and expected next steps; it is present to assist with
debugging.
- `tsconfig.json`:
- `compilerOptions.useDefineForClassFields: false` is required to make TSC use the "classic" form
of field definition when compiling class definitions. Storybook does not handle the ESNext
proposed definition mechanism (yet).
- `compilerOptions.plugins.ts-lit-plugin.rules.no-unknown-tag-name: "off"`: required to support
rapidoc, which exports its tag late.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-missing-import: "off"`: lit-analyzer currently
does not support path aliases very well, and cannot find the definition files associated with
imports using them.
- `compilerOptions.plugins.ts-lit-plugin.rules.no-incompatible-type-binding: "warn"`: lit-analyzer
does not support generics well when parsing a subtype of `HTMLElement`. As a result, this threw
too many errors to be supportable.
The Executor is the heart of the system. It executes Flows.
A _Flow_ in authentik is the workflow that accomplishes a specific SSO-oriented task such as logging
in, logging out, or enrolling as a new user, among others.
The Executor starts by examining the current URL for the `flowSlug`, and sends a request for a
_Challenge_ to the server. Upon the response, the Executor loads the corresponding _Stage_: the UI
component responsible for showing the challenge to the user. When the user performs the requested
action the input is sent to the server, which issues a new Challenge. This Challenge may be the same
one with error messages, or the next one in the workflow. This process repeats until the user
reaches the end of the Flow, at which point the task is complete or failed.
The architecture for the Executor is straightforward:
- The HTML Document
- The Locale Selector
- The Inspector
- The Executor
- The current Stage
A Stage may have interior stages or components. The Identification Stage is the most complex of our
stages. It usually shows the Username field, and in some configurations it _can_ show the Password
field; in that case, the password component exists to allow the user to "show password". It may also
host the Captcha and Passkey stages within, to complete the initial task of determining and
validating a user's identity.
### User and Admin Interfaces
The architecture of these interfaces is more complex. In both cases, the user is assumed to have
logged in and so is said to have a _Session_. The architecture is structured:
- The HTML Document
- The Interface
- License: a context handler for the site's enterprise license status
- Session: a context handler for the user's current session. This mostly the `user` identity
- Version: a context handler for the current version of authentik
- Notifications: a context handler for outstanding messages sent from the server to the user
- Capabilities: a list of features that the current user may use. List includes "can save
reports," "can use debugging feature," "can use enterprise features."
- The Application:
- Header
- Sidebar
- Router
- CRUD interfaces to features of the system:
- Dashboard
- Logs
- Configurations
- Flows, Stages & Policies
- Users & Group
- IDP Sources
- Everything else!
### Miscellaneous interfaces
There are three miscellaneous interfaces:
#### API browser
A single page application that loads our schema and allows the user to experiment with it. Uses the
[RapiDoc](https://rapidocweb.com/) app.
#### Loading
The Django application is wrapped in a proxy server for caching and performance; while it is in
start-up mode, the proxy serves this page, which just says "The application is loading" with a
pretty animation.
#### SFE: Simplified Flow Executor
The SFE is a limited version of the Flow Executor written to use [jQuery](https://jquery.com/). It
supports only login operations, and is meant for places where the login is embedded in an Office365
or MicrosoftTeams settings, as those use Trident (Internet Explorer) for their web-based login.
## Front-end foundations
### CSS
Our current CSS is provided by [Patternfly 4](https://v4-archive.patternfly.org/v4/). There are two
different layers of CSS.
The first is the Global CSS that appears in the `<head>`. This defines the basic look: theme,
start-up, reset, and fonts. It also provides the [CSS Custom
Properties](https://docs.goauthentik.io/brands/custom-css/) that control the look and feel of the
rest of an Interface.
The second is per-component CSS. This is linked into each component using [Adopted
Stylesheets](https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets).
> This recipe has led to some significant awkwardness. Information from the outside does not pierce
> the shadowDOM, so Patternfly-Base is linked into every component just to provide the box model,
> reset, basic functionality, and behavioral modifiers. The interior of our components is cluttered
> with lots of patternfly classes.
### Elements
Elements are custom web components that authentik has written that supply advanced behaviors to
common operations, as well as API-independent complex components such as rich drop-downs, dual-pane
selectors, toggles, switches, and wizards. At least, that's the idea. We are still untangling.
### Components
Components are custom web components that authentik has written that are API-aware and that supply
business logic to perform validation, permissioning, and selective display.
## Adding a new feature (developer's guide)
As a thin client, the primary task will either be adding a new CRUD vertical or extending and
enhancing an existing one. (If the elements, components, API, and so on represent the horizontal
layers of an application, a single CRUD task is the "vertical slice" through these.) Our Django
application presents collections of objects from which the user may pick one to view, update, or
delete.
The web component in `./elements/table` is used to display, well, tables of components. A new
feature begins by inheriting the `Table` class and providing two things: the API call to retrieve
the objects, and a method describing a row for the table. This is the retrieval for our Role-Based
Access Controls (RBAC).
```
async apiEndpoint(): Promise<PaginatedResponse<Role>> {
return new RbacApi(DEFAULT_CONFIG).rbacRolesList({
...(await this.defaultEndpointConfig()),
managedIsnull: this.hideManaged ? true : undefined,
});
}
```
The complete list of APIs available can be found in `node_modules/@goauthentik/api/src/apis`.
A row returns an array of cells:
```
row(item: Role): SlottedTemplateResult[] {
return [
html`<a href="#/identity/roles/${item.pk}">${item.name}</a>`,
html`<div>
<ak-forms-modal>
<span slot="submit">${msg("Update")}</span>
<span slot="header">${msg("Update Role")}</span>
<ak-role-form slot="form" .instancePk=${item.pk}> </ak-role-form>
<button slot="trigger" class="pf-c-button pf-m-plain">
<pf-tooltip position="top" content=${msg("Edit")}>
<i class="fas fa-edit" aria-hidden="true"></i>
</pf-tooltip>
</button>
</ak-forms-modal>
</div>`,
];
}
```
This example shows the use of the `modal` dialogue to show the "update role" form. Deciding to use
a modal or to move to a different page is a matter of taste, but mostly rests on how large the form
is. If it's likely to have internal scrolling, opt for a separate page.
For complex objects that have a lot of detail or subsidiary lists of features (such as Flows),
provide a separate View page for each one. We have a specified display standard encapsulated in our
`DictionaryList` component.
Creation and Updating are handled using the web component parent in `./elements/forms`. Like
tables, a child component inherits and extends the Form class, providing three features: how to
_retrieve_ the object, how to _send_ the object, and what to ask for. (RBAC is small enough, it's
useful as an example):
```
loadInstance(pk: string): Promise<Role> {
return new RbacApi(DEFAULT_CONFIG).rbacRolesRetrieve({
uuid: pk,
});
}
async send(data: Role): Promise<Role> {
if (this.instance?.pk) {
return new RbacApi(DEFAULT_CONFIG).rbacRolesPartialUpdate({
uuid: this.instance.pk,
patchedRoleRequest: data,
});
}
return new RbacApi(DEFAULT_CONFIG).rbacRolesCreate({
roleRequest: data,
});
}
protected override renderForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("Name")} required name="name">
<input
type="text"
value="${ifDefined(this.instance?.name)}"
class="pf-c-form-control"
required
/>
</ak-form-element-horizontal>`;
}
```
The `send` shows two different modes: If the existing instance has an identity, this is an update;
otherwise it's a creation request.
These are _simple_ examples, naturally, and our application can get much more complicated. The
`./admin/flows` vertical is one of the most complex, including:
- A per-flow view page with a [Mermaid](https://mermaid.js.org/) diagram to show a Flow's Stages
- A sub-table of the Flow's Policies, with the ability to edit each Policy or its Bindings
- A sub-table of the Flow's Stages with the ability to edit each Stage or a Stage's Binding directly
- A sub-table of the Flow's Permissions
## Choosing To Use A Custom Component (developer's guide)
Some of our server-side objects come with lists. When editing a list, we suggest:
- If it's a simple list and there's only one choice, use `<select>`
- If it's from the server and it's possible there are more than 100 items, use SearchSelect. It
has features for showing complex list objects and narrowing down search items.
- If the user can select multiple choices, use DualSelect
### License

View File

@@ -0,0 +1,30 @@
# 01 Foundations: Language
Date: 2026-05-01 (May 1st, 2026)
## The web application
The authentik web-based front-end is written in Typescript. We are currently targeting Typescript
7.0, aka "TSGO," for its speed and compatibility. We chose Typescript because our experience has
been that the type system, when used reliably, can prevent a wide class of errors, especially when
negotiating with the authentik API as generated by OpenAPI.
- `mode: strict` is non-negotiable.
- `useDefineForClassFields` is required for Lit decorator compatibility. See the Lit documentation
[Typescript class fields for reactive
properties](https://lit.dev/docs/components/properties/#:~:text=Set%20the%20useDefineForClassFields%20compiler%20option%20to%20false)
for details.
## Tooling
Most of our internal tooling in the `./web/scripts` folder is written in JavaScript, not Typescript,
to avoid the chicken-and-egg problem of needing build scripts to build build scripts. To facilitate
checking, we enable `checkJs` and [jsdoc supported
types](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#param-and-returns) in
our tooling and script files, and we check them rigorously.
## Guidance:
Whenever tempted to use `any`, use `unknown` and a [type
predicate](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
(sometimes called a "type guard") instead.

View File

@@ -0,0 +1,27 @@
# 02 Foundations: Build Tools
Date: 2026-05-01 (May 1st, 2026)
## Esbuild and TSGo
In 2024, the web UI used Rollup and TSC as its primary build tools. Building the entire UI for
release took as many as three minutes.
ESBuild can both produce running Javascript from Typescript, and perform all of the bundling
required to support the authentik WebUI. Switching to ESBuild reduced build time to 5 _seconds_. TSC
has been relegated to the `no-emit` strategy of type-checking but not code-producing.
One complication in our code is that our web component foundation, Lit, has an awkward
CSS-in-Javascript format incompatible with the build tools intended to support React, and the
ESBuild plug-in to handle it is custom.
As of this writing, Typescript 7.0, aka "TSGo," is currently in beta. When it is released, we expect
to both reassess this strategy and examine alternative build strategies. We prefer to hew as close
to the Typescript standard as possible, and the standard is set by the Typescript team.
## Wireit
We have chosen to use Wireit because it provides a finer degree of control over build order and
provides a caching strategy. This significantly speeds up rebuilding during development versus using
NPM's own builds. Use Wireit _only_ when you need the cache or dependency order to be strict; for
baseline builds, prefer writing directly into the `scripts` section of `package.json`.

View File

@@ -0,0 +1,20 @@
# 01 Foundations: Workspaces
Date: 2026-05-02 (May 2st, 2026)
## Workspaces
In order to promote the use and development of a product by the widest community possible, we
default to using NPM workspaces, since it is the most common too possible.
Provide a separate workspace when:
1. The project is support that applies across multiple other workspaces, rather than being a part of
an application directly. `./packages/core` is the example.
2. The project is a polyfill or library that is needed across all the applications supported by the
front-end. `./packages/formdata-polyfill` is the example.
3. The project is an application that has radically different requirements from the standard set of
applications. `./packages/sfe` exists to support only the Login Flow with the Internet Explorer
11-based rendering engine, which is still embedded in some older Microsoft products we cannot
afford to ignore.

View File

@@ -0,0 +1,12 @@
# 01 Foundations: Import Strategies
Date: 2026-05-02 (May 2st, 2026)
## Import Strategies
`package.json` defines a large number of import paths that reach into the `src` folder. We use
NodeJS subpaths prefixed with `#`, such as `#fonts`, `#elements`, or `#flow` to isolate subsections
of the frontend. This strategy is intended to facilitate directory restructuring without having to
do mass search-and-replace ops, and as a precursor to further mono-repo-ifying the codebase.
We recommend using barrel files only to export the intended API of a defined subsection.

View File

@@ -0,0 +1,69 @@
# 01 Foundations: Code Quality
Date: 2026-05-02 (May 2st, 2026)
## Code Linting
We _like_ our guardrails. We use ESLint with as many plug-ins as we can reasonably stuff into it for
our checks, such as `eslint-plugin-lit` and `eslint-plugin-wc`, plus `lit-analyzer`.
## Code Formatting
We use `prettier` to enforce a coding style and to catch some fundamental syntax errors. The current
`prettier` configuration correctly formats Lit's HTML-in-JS and CSS-it-JS use cases, as well as all
the Typescript we can throw at it.
## Lockfile
We have a custom script in `./scripts/lint-lockfile.sh` that checks to ensure that every packages as
a resolved hash.
## Type Checking
Although we use ESBuild to convert and bundle our Typescript into JavaScript, we use the stock
Typescript compiler, `tsc`, to check our types. We maintain a default configuration with `use
strict`.
## Testing
We do have tests, but they are primitive. We are very much in a move-fast and try hard not to break
things. We strongly recommend that every PR include a description of how a peer would test the
product manually to validate that it does what the PR claims it does.
Adding to the library of end-to-end tests is a critical mission.
## Your eyes, and the eyes of your peers
For all that we do like our guardrails, nothing surpasses peer review.
## AI Review
We have had mixed results using AI tools such as Claude and Copilot to vet our code. Claude,
especially, can be very good at pointing out shortcomings and missed opportunities in a pull
request, but it can also generate a lot of false positives or trivial issues. We recommend reading
AI reviews with caution.
## AI Review Strategy
That said, this is the current template for a code review prompt. Start with the _target_ branch,
then download the patch file into the project root. You can easily download the patch file by
navigating to the Github PR and appending `.patch` to it, for example:
`https://github.com/goauthentik/authentik/pull/21868.patch`
We use this template:
> Keep the tone neutral-professional-skeptical, the voice of an expert. Avoid excessive enthusiasm.
> This is the root folder for the authentik single sign-on server.
>
> Read the patch file `./21868.patch`. This [community-provided] patch [describe the patch here in
> your own words, using only one or two sentences].
>
> Task 1: Provide a high-level summary of the effect of applying `./21868.patch` Point out any
> shortcomings or security considerations.
>
> Task2: If no tests are provided in the patch, describe how these changes could be tested.
Edit the patch number, add or remove "community-provided" as needed, and include your best
understanding of what the patch claims to do in the second paragraph. Having a strong template that
you hand-edit before running seems to work much better than using a generic template in a Claude
skill.

View File

@@ -10,7 +10,11 @@
"allowSyntheticDefaultImports": true,
"emitDeclarationOnly": true,
"experimentalDecorators": true,
// See https://lit.dev/docs/components/properties/
// `useDefineForClassFields` is required for Lit decorator compatibility.
// See the Lit documentation
// [Typescript class fields for reactive properties](https://lit.dev/docs/components/properties/#:~:text=Set%20the%20useDefineForClassFields%20compiler%20option%20to%20false)
// for details.
"useDefineForClassFields": false,
"target": "esnext",
"module": "preserve",