Extremely lightweight OpenID Connect server
Kestrel 1128a3ffc2 Correctly build multi-arch image manifest. | 1 month ago | |
---|---|---|
.cargo | 1 month ago | |
src | 1 month ago | |
static | 1 year ago | |
tmpl | 1 month ago | |
.gitignore | 1 month ago | |
.vimrc | 2 years ago | |
Cargo.lock | 1 month ago | |
Cargo.toml | 1 month ago | |
Containerfile | 1 month ago | |
LICENSE | 1 year ago | |
Makefile | 1 month ago | |
README.md | 1 month ago | |
basic_config.toml | 1 year ago | |
simple-setup.sh | 6 months ago | |
uidc.toml | 1 month ago |
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:
... then uidc might be what you're looking for.
(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.
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
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.
uidc implements a simple role-based authentication schema. it works as follows:
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"
]
}
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"
]
}
Core components of uidc:
schema
: database schema types for type-safe use of SQLite via micrormserver
: server runtime*_management
: implementations of manipulation helpers for CLI and/or REST API interfacesuidc is licensed under a 4-clause BSD license.