OIDC Pages is a static HTML document server that integrates OpenID Connect (OIDC) for authentication and per-document authorization (permissions). OIDC Pages is designed to work seamlessly with documentation tools such as Sphinx, Doxygen, and mdbook, and can be used with any static HTML content.
- Integrates with Keycloak
- Respects system dark / light settings
- NixOS module provided
- Supports dynamically uploaded documents
- Secure by default
- Likely incompatible out-of-the-box with other OIDC providers
- Sessions are stored in-memory and erased on restart
- Not intended for serving untrusted content
There are two assumptions that make this keycloak-specific.
- The OIDC specification doesn't define a type for the access token, keycloak uses a JSON web token which is the de-facto standard.
- The OIDC specification doesn't provide a standard way to read roles.
Roles are assumed to be under
resource_access
-><client_id>
->roles
.
Majority of OIDC providers use a JWT for the access token, the only modifications necessary should be how to obtain roles.
These features may or may not happen.
- Public pages
- Persistent user sessions
- Refresh tokens
- API for uploading pages over https
- Pretty error pages
- Serving pages from subdomains instead of paths
- Pictorial preview of pages
Please report vulnerabilities to my git committer email.
- Language: rust
- Asynchronous runtime: tokio
- Web framework: axum
- Session management: tower-sessions
- Templating engine: askama
- OpenID Connect library: openidconnect-rs
- Favicon provided by Flowbite
This is designed to work with NixOS, but should work on any Linux OS with systemd.
You need to bring a reverse proxy for TLS, I suggest nginx.
- Create and enable an OpenID Connect client in your realm
- Root URL:
https://pages.company.com
- Home URL:
https://pages.company.com
- Valid redirect URIs:
https://pages.company.com/callback
- Client authentication:
On
- Authorization:
Off
- Authentication flow:
Standard flow
(all others disabled)
- Root URL:
- Create roles for the newly created client
- The
admin
role can view all pages - All other roles grant permissions to pages in a directory matching the role name
- The
- Create a dedicated audience mapper for the newly created client
- Navigate to Clients ->
<client_id>
-> Client scopes -><client_id>-dedicated
-> Configure a new mapper -> Audience - Name:
aud-mapper-<client_id>
- Included Client Audience:
<client_id>
- Add to ID token:
On
- Add to access token:
On
- Add to lightweight access token:
Off
- Add to token introspection:
On
- Navigate to Clients ->
Reference nixos/module.nix
for a complete list of options,
below is an example of my configuration.
{
oidc_pages,
config,
...
}: let
pagesDomain = "pages.company.com";
in {
# import the module, this adds the "services.oidc_pages" options
imports = [oidc_pages.nixosModules.default];
# add the overlay, this puts "oidc_pages" into "pkgs"
nixpkgs.overlays = [oidc_pages.overlays.default];
# use nix-sops to manage secrets declaratively
# https://github.com/Mic92/sops-nix
sops.secrets.oidc_pages.mode = "0400";
# reference module for descriptions of configuration
services.oidc_pages = {
enable = true;
environmentFiles = [config.sops.secrets.oidc_pages.path];
# give nginx access to oidc_pages.socket
socketUser = config.services.nginx.user;
settings = {
public_url = "https://${pagesDomain}";
issuer_url = "https://sso.company.com/realms/company";
client_id = "pages";
pages_path = "/var/www/pages";
log_level = "info";
};
};
# use NGINX as a reverse proxy to provide a TLS (https) interface
networking.firewall.allowedTCPPorts = [443];
services.nginx = {
enable = true;
virtualHosts."${pagesDomain}" = {
onlySSL = true;
locations."/".proxyPass = "http://unix:${config.services.oidc_pages.bindPath}";
};
};
}