Extremely lightweight OpenID Connect server

Kestrel 1128a3ffc2 Correctly build multi-arch image manifest. 2 months ago
.cargo 48ca3a8398 Updated Containerfile to build for multiple architectures more seamlessly via a makefile. 2 months ago
src 1128a3ffc2 Correctly build multi-arch image manifest. 2 months ago
static cfea7d2b85 Implemented basic user management updating. 1 year ago
tmpl 4d45d48eb7 Support external authentication via github. 2 months ago
.gitignore 4d45d48eb7 Support external authentication via github. 2 months ago
.vimrc 1118273564 Changes pending swap to static login pages. 2 years ago
Cargo.lock 48ca3a8398 Updated Containerfile to build for multiple architectures more seamlessly via a makefile. 2 months ago
Cargo.toml 48ca3a8398 Updated Containerfile to build for multiple architectures more seamlessly via a makefile. 2 months ago
Containerfile 48ca3a8398 Updated Containerfile to build for multiple architectures more seamlessly via a makefile. 2 months ago
LICENSE 45a21e5902 Update README and set version to v0.0.1. 1 year ago
Makefile 1128a3ffc2 Correctly build multi-arch image manifest. 2 months ago
README.md a744a24ec7 Remove config storage from database and move to external TOML file. 3 months ago
basic_config.toml 3cf03ebddd Add basic config for local testing purposes. 1 year ago
simple-setup.sh bc4f8eb1e0 Minor cleanups and update version number to v0.0.2. 7 months ago
uidc.toml a744a24ec7 Remove config storage from database and move to external TOML file. 3 months ago

README.md

uidc, a lightweight OpenID Connect server

uidc is a lightweight OpenID Connect (OIDC) server, implementing the OpenID Connect Core specification. It's designed as a replacement for heavyweight systems like Keycloak when you want to have SSO without a dedicated machine running it, for example on personal or small-scale infrastructure. Since it supports OIDC, it also functions perfectly well as an OAuth2 provider.

uidc is designed in an opinionated fashion, eschewing general flexibility for simplicity, which means it is most definitely not a one-size-fits-all solution. If you're planning on dealing with hundreds of thousands of users or complicated authentication flows, look elsewhere. But, if you want:

  • lightweight (runtime memory usage is less than 20MB, even under moderate load, and CPU usage is minimal)
  • simple (single statically-linked executable and minimal supporting files)
  • ready to use out of the box (as long as your authentication needs fall into the first 95% of use cases)
  • easily configured (configuration is done via a single TOML file and a CLI with tab completion)
  • flexible, within limits (implements generic role-based access control)

... then uidc might be what you're looking for.

Getting started

(The following will assume that $UIDC points to the uidc executable.)

The general format of a uidc invocation runs something like the following:

./path/to/uidc --config-path $PATH_TO_CONFIG <noun> <verb> <options>

If a config file is not explicitly passed via --config-path, it will default to using a file called uidc.toml in the current directory; you can also set the environment variable UIDC_CONFIG if that's more convenient. The config path is elided from the following examples for brevity.

Initial setup

Start with a very simple configuration file, something like the following:

db_path = "uidc.db"
base_url = "https://externally-visible-url"

Then initialize the database and create a signing key:

$UIDC init
$UIDC key generate rsa2048

Create yourself a user and add a password (and 2FA if you want, by passing a -pt instead of -p):

$UIDC user create <username>
$UIDC user update-auth -p <username>

Finally, create an OIDC client:

$UIDC client create example-client

And then run the server! By default, it listens on port 2114, but that can be changed with --port.

$UIDC serve

Realms

uidc implements 'realms', or independent authentication domains. By default, everything is in the primary realm, but you can switch to another realm with -r. For example:

$UIDC realm create secondary
$UIDC -r secondary user create someuser

Each realm has its own set of users, groups, roles, and clients.

RBAC

uidc implements a simple role-based authentication schema. it works as follows:

  • users are part of groups
  • groups and OIDC clients have roles (permissions) assigned to them
  • roles are further grouped into scopes

When authentication tokens (or refresh tokens!) are created, a list of scopes is requested. The resulting set of roles in the authentication token is the following:

attached_roles(user_groups) ∩ attached_roles(given_scopes)

That is, a role is only included in the authentication token if it is both a) requested during the token grant, and b) available to the user through a group they are a member of.

For illustration, let's create three roles, put them into two groups, and assign each group to a single user. We'll also put all the roles into a scope to make all this available to clients.

# users
$UIDC user create user1
$UIDC user create user2

# roles
$UIDC role create roleA
$UIDC role create roleB
$UIDC role create roleC

# groups
$UIDC group create group1
$UIDC group create group2

$UIDC group attach-user group1 user1
$UIDC group attach-role group1 roleA
$UIDC group attach-role group1 roleB

$UIDC group attach-user group2 user2
$UIDC group attach-role group2 roleB
$UIDC group attach-role group2 roleC

# scope
$UIDC scope create example-scope
$UIDC scope attach-role example-scope roleA
$UIDC scope attach-role example-scope roleB
$UIDC scope attach-role example-scope roleC

Now, if we generate some authentication tokens for user1 and user2 and peek at the JWT claims, we'll see the roles split between the two users:

$UIDC token generate-auth --client example-client --username user1 --scopes example-scope \
    | awk -F. '{print $2}' | base64 -d | jq
{
  "sub": "user1",
  "iss": "https://externally-visible-url/primary",
  "aud": "example-client",
  "iat": 1699926032,
  "exp": 1699926632,
  "roles": [
    "roleA",
    "roleB"
  ]
}

and

$UIDC token generate-auth --client example-client --username user2 --scopes example-scope \
    | awk -F. '{print $2}' | base64 -d | jq
{
  "sub": "user2",
  "iss": "https://externally-visible-url/primary",
  "aud": "example-client",
  "iat": 1699926035,
  "exp": 1699926635,
  "roles": [
    "roleB",
    "roleC"
  ]
}

Provider configuration

Each realm gets its own set of token endpoints, available at https://uidc-base-url/<realm>/.well-known/openid-configuration. An example configuration might look something like the following:

{
  "authorization_endpoint": "https://base-url/primary/oidc/authorize",
  "id_token_signing_alg_values_supported": [
    "EdDSA"
  ],
  "issuer": "https://base-url/primary",
  "jwks_uri": "https://base-url/primary/oidc/jwks",
  "response_types_supported": [
    "code",
    "id_token",
    "token id_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "token_endpoint": "https://base-url/primary/oidc/token",
  "token_endpoint_auth_signing_alg_values_supported": [
    "EdDSA"
  ]
}

Implementation and licensing details

Core components of uidc:

  • schema: database schema types for type-safe use of SQLite via microrm
  • server: server runtime
  • *_management: implementations of manipulation helpers for CLI and/or REST API interfaces

uidc is licensed under a 4-clause BSD license.