# OIDC Pages OIDC pages serves static HTML documents with OIDC for authentication and per-document authorization (permissions). This is designed to be used with HTML generated from tools such as sphinx, doxygen, or mdbook, but works with any static HTML. ## Screenshots ![OIDC Pages index](/screenshots/index.png?raw=true "OIDC Pages index") ## Features * Integrates with Keycloak * Respects system dark / light settings * NixOS module provided * Supports dynamically uploaded documents * Secure by default ### Limitations * Likely incompatible out-of-the-box with other OIDC providers * Sessions are stored in-memory and will be erased on restart * Not intended for serving untrusted content ### Adapting to Other OIDC Providers There are two assumptions that make this keycloak-specific. 1. The OIDC specification does not define a type for the access token, keycloak uses a JSON web token which is the de-facto standard. 2. The OIDC specification does not provide a standard way to read roles. Roles are assumed to be under `resource_access` -> `` -> `roles`. Majority of OIDC providers use a JWT for the access token, the only modifications necessary should be how to obtain roles. ### Planned Features These features may or may not happen. * Public documents * Persistent user sessions * Refresh tokens * Serving pages from S3 * Listening on a Unix domain socket when [axum#2479](https://github.com/tokio-rs/axum/pull/2479) is resolved * [Pretty error pages](https://docs.rs/tower-http/0.5.2/tower_http/services/struct.ServeDir.html#method.not_found_service) * Serving pages from subdomains instead of paths * Pictorial preview of pages ## Security Please report vulnerabilities to my git committer email. ## Technology * Language: [rust](https://www.rust-lang.org) * Asynchronous runtime: [tokio](https://tokio.rs) * Web framework: [axum](https://github.com/tokio-rs/axum) * Session management: [tower-sessions](https://github.com/maxcountryman/tower-sessions) * Templating engine: [askama](https://github.com/djc/askama) * OpenID Connect library: [openidconnect-rs](https://github.com/ramosbugs/openidconnect-rs) * Favicon provided by [Flowbite](https://flowbite.com/icons) ## Usage This is designed to be used with [NixOS], but should work on any Linux OS with systemd. You will need to bring a reverse proxy for TLS, I suggest [nginx]. ### Keycloak Configuration * 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) * Create roles for the newly created client * The `admin` role can view all pages * All other roles allow users to access pages with a directory of the same name * Create a dedicated audience mapper the newly created client * Navigate to **Clients** -> `` -> **Client scopes** -> `-dedicated` -> **Configure a new mapper** -> **Audience** * Name: `aud-mapper-` * Included Client Audience: `` * Add to ID token: `On` * Add to access token: `On` * Add to lightweight access token: `Off` * Add to token introspection: `On` ### NixOS Configuration Reference `nixos/module.nix` for a complete list of options, below is an example of my configuration. ```nix { oidc_pages, config, ... }: let bindAddr = "127.0.0.1:38443"; 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]; settings = { public_url = "https://${pagesDomain}"; issuer_url = "https://sso.company.com/realms/company"; client_id = "pages"; pages_path = "/var/www/pages"; log_level = "info"; bind_addrs = [bindAddr]; }; }; # 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://${bindAddr}"; }; }; } ``` [NixOS]: https://nixos.org [nginx]: https://nginx.org