LibreGraph Connect implements an OpenID provider (OP) with integrated web login and consent forms.
LibreGraph Connect has it origin in Kopano Konnect and is meant as its vendor agnostic successor.
- Go
- React
Lico provides services based on open standards. To get you an idea what Lico can do and how you could use it, this section lists what is implemented, with the limits worth knowing before you build on it.
- OpenID Connect Core 1.0
- OpenID Connect Discovery 1.0
- OpenID Connect Front-Channel Logout 1.0
- OpenID Connect Session Management 1.0
- OpenID Connect Dynamic Client Registration 1.0 -
stateless, the registration is signed into the
client_iditself
- RFC 6749 - The OAuth 2.0 Authorization Framework - the authorization code and refresh token grants
- RFC 6750 - Bearer Token Usage -
access tokens are bearer tokens, carried in the
Authorizationheader - RFC 7591 - Dynamic Client Registration -
behind
--allow-dynamic-client-registration, without the registration management of RFC 7592 - RFC 7636 - PKCE -
S256only,plainis refused - RFC 8252 - OAuth 2.0 for Native Apps - loopback redirect URIs on any port, and private-use URI schemes
- RFC 8414 - Authorization Server Metadata - the same document the OpenID Connect discovery endpoint serves
- RFC 8707 - Resource Indicators -
the
resourceparameter binds an access token to the resource it is for - OAuth 2.0 Multiple Response Type Encoding Practices
- RFC 7517 - JSON Web Key
- RFC 7519 - JSON Web Token
- RFC 7693 - The BLAKE2 Cryptographic Hash
- JOSE algorithm registry
- NaCl secretbox
Make sure you have Go 1.25 or later installed, as declared in go.mod. This
project uses Go Modules.
Lico also includes a modern web app, which needs one additional build dependency, assumed to be in your $PATH.
- bun - Bun 1.4.0 or later
Regenerating the favicon from identifier/src/images/lico-icon.svg with
make -C identifier/src/images icons additionally needs the two below. No
other target uses them, so an ordinary build does not require either.
- magick, or convert on ImageMagick 6 - ImageMagick
- rsvg-convert - librsvg
To build Lico, a Makefile is provided, which requires make.
When building, third party dependencies will be fetched from the Internet if not there already.
git clone <THIS-PROJECT> lico
cd lico
make
Some optional build dependencies are required for linting and continuous integration. Those tools are mostly used by make to perform various tasks and are expected to be found in your $PATH.
- golangci-lint - golangci-lint
- gotestsum - gotestsum
- gocover-cobertura - gocover-cobertura
The image carries Go, Bun and the optional tools above, so nothing beyond the container engine is needed.
docker build -t licod-builder -f Dockerfile.build .
docker run -it --rm -u $(id -u):$(id -g) -v $(pwd):/build licod-builder
With rootless podman, use --userns=keep-id instead of -u. Podman maps your
user to root inside the container, so passing your own uid lands on an
unprivileged one that cannot write the mounted tree.
podman build -t licod-builder -f Dockerfile.build .
podman run -it --rm --userns=keep-id -v $(pwd):/build licod-builder
Lico can provide user login based on available backends.
All backends require certain general parameters to be present. Create a RSA
key-pair file with openssl genpkey -algorithm RSA -out private-key.pem -pkeyopt rsa_keygen_bits:4096
and provide the key file with the --signing-private-key parameter. Lico can
load PEM encoded PKCS#1 and PKCS#8 key files and JSON Web Keys from .json files
If you skip this, Lico will create a random non-persistent RSA key on startup.
To encrypt certain values, Lico needs a secure encryption key. Create a
suitable key of 32 bytes with openssl rand -out encryption.key 32 and provide
the full path to that file via the --encryption-secret parameter. If you skip
this, Lico will generate a random key on startup.
To run a functional OpenID Connect provider, an issuer identifier is required.
The iss is a full qualified https:// URI pointing to the web server which
serves the requests to Lico (example: https://example.com). Provide the
Issuer Identifier with the --iss parameter when starting Lico.
Furthermore to allow clients to utilize the Lico services, clients need to
be known/registered. Lico reads them from a static configuration file which
allows clients and their allowed urls to be registered. See the the example at
identifier-registration.yaml.in. Copy and modify that file to include all
the clients which should be able to use OpenID Connect and/or OAuth2 and start
Lico with the --identifier-registration-conf parameter pointing to that
file. Without any explicitly registered clients, Lico will only accept clients
which redirect to an URI which starts with the value provided with the --iss
parameter.
A client which the operator has never heard of can also register itself, when
Lico runs with --allow-dynamic-client-registration. Such a registration is
stateless: it is signed into the client_id it returns, nothing is stored, and
every one of them has to use PKCE. A client which asks for
token_endpoint_auth_method: none is a public client and is issued no secret,
whether it registered itself or was written into the file. Use --require-pkce
to ask the same of every client, and --dynamic-client-secret-expiration to
expire the credentials of a dynamic registration, which by default do not
expire.
Lico can issue access tokens for an API which is not Lico itself, a Model Context Protocol server for example. Such a resource is registered in the same file as the clients:
resources:
- id: https://mcp.example.com/mcp
name: Example MCP server
scopes:
- Example.Mcp.Tools
implicit_scopes:
- LibgreGraph.UUIDThe id is the canonical URI of that resource, which is what a client sends as
the resource parameter of RFC 8707 and what ends up in the aud claim of the
access token, so it has to be exactly the value the resource publishes for
itself. A client is identified in azp instead, and a request which names no
resource is unaffected: its tokens stay bound to the client, as before.
The scopes are the ones that resource accepts. A request naming it is
narrowed to them, so a scope missing there never reaches the token, not even
when a client registration adds it implicitly; openid and offline_access
are exempt, being about the client rather than a permission of the resource.
The implicit_scopes are granted whether a client asked for them or not, for
what has to reach the resource without every client knowing about it. Every
scope named in either list must also be defined in the file given with
--identifier-scopes-conf, or Lico refuses to start.
The resource publishes its own
RFC 9728 metadata, naming Lico
in authorization_servers, which is how a client finds its way here. Lico
neither serves nor fetches that document: it is the resource's own statement
about itself.
A tool can be used to create keys for Lico and also to validate tokens to ensure correct operation is Step CLI. This helps since OpenSSL is not able to create or validate all of the different key formats, ciphers and curves which are supported by Lico.
Here are some examples relevant for Lico.
step crypto keypair 1-rsa.pub 1-rsa.pem \
--kty RSA --size 4096 --no-password --insecure
step crypto keypair 1-ecdsa-p-256.pub 1-ecdsa-p-256.pem \
--kty EC --curve P-256 --no-password --insecure
step crypto jwk create 1-eddsa-ed25519.pub.json 1-eddsa-ed25519.key.json \
-kty OKP --crv Ed25519 --no-password --insecure
echo $TOKEN_VALUE | step crypto jwt verify --iss $ISS \
--aud playground-trusted.js --jwks $ISS/konnect/v1/jwks.json
Take a look at Caddyfile.example on the URL endpoints provided by Lico and
how to expose them through a TLS proxy.
The base URL of the frontend proxy is what will become the value of the --iss
parameter when starting up Lico. OIDC requires the Issuer Identifier to be
secure (https:// required).
The provider meta data is served at /.well-known/openid-configuration and,
identically, at /.well-known/oauth-authorization-server, where an OAuth 2.0
client which does not speak OpenID Connect discovery looks for it first. A
proxy which forwards by explicit path has to forward both.
Generic backend support is available through the LibreGraph API. Any service can provide the required endpoints and Lico connects to them.
export LIBREGRAPH_URI=http://your-backend.local:5050
bin/licod serve --listen=127.0.0.1:8777 \
--iss=https://mylico.local \
libregraph
This assumes that Lico can directly connect to an LDAP server via TCP.
export LDAP_URI=ldap://myldap.local:389
export LDAP_BINDDN="cn=admin,dc=example,dc=local"
export LDAP_BINDPW="its-a-secret"
export LDAP_BASEDN="dc=example,dc=local"
export LDAP_SCOPE=sub
export LDAP_LOGIN_ATTRIBUTE=uid
export LDAP_EMAIL_ATTRIBUTE=mail
export LDAP_NAME_ATTRIBUTE=cn
export LDAP_UUID_ATTRIBUTE=uidNumber
export LDAP_UUID_ATTRIBUTE_TYPE=text
export LDAP_FILTER="(objectClass=organizationalPerson)"
bin/licod serve --listen=127.0.0.1:8777 \
--iss=https://mylico.local \
ldap
This project includes a Dockerfile which can be used to build a Docker
container from the locally build version. Similarly the Dockerfile.release
builds the Docker image locally from the latest release download.
docker build -t licod .
docker build -f Dockerfile.release -t licod .
The complete API specification is available in OpenAPI 3.0 format at docs/libregraph-connect-api-v1.yaml, covering all OIDC, OAuth2, authentication, and SAML2 endpoints with detailed schemas and examples.
make test
As Lico includes a web application (identifier), a Caddyfile.dev file is
provided which exposes the identifier's web application directly via a
webpack dev server.
Lico is built stripped and without debug symbols by default. To build for debugging, compile with additional environment variables which override/reset build optimization like this
LDFLAGS="" GCFLAGS="all=-N -l" ASMFLAGS="" make cmd/licod
The resulting binary is not stripped and sutiable to be debugged with Delve.
To connect Delve to a running Lico binary you can use the make dlv command.
Control its behavior via DLV_* environment variables. See the Makefile source
for details.
DLV_ARGS= make dlv
To use remote debugging, pass additional args like this.
DLV_ARGS=--listen=:2345 make dlv
See LICENSE.txt for licensing information of this project.