Skip to content

Configuration

SaschaZeGerman edited this page Jan 31, 2024 · 37 revisions

Configuration

Loginbuddy's configuration is file based. Here is an overview:

alt Loginbuddy configuration files

Tip: I recommend to launch the samples project (see Quick Start) and configure that for evaluation purposes to get started!

All configuration files are located here:

  • {loginbuddy}/net.loginbuddy.service/src/main/resources

Note: Clients and providers are configured within the same file, named config.json, and has this structure:

{ "clients": [], "providers": [] }

Below we will discuss the providers and clients section separately. At the end complete examples are shown.

For everything listed you will find examples in the FAQ document. It really is not that hard, although it may look like it!

1. Configure OpenID Providers in Loginbuddy

This is required for outbound connections, as in Loginbuddy connecting to OpenID Providers.

All configuration is done in the file config.json. If you are using the samples project find it at ./docker-build/add-ons/loginbuddy/config.json.

The overview below displays the JSON configuration keys within the section providers. This is a JSONArray and may contain multiple configurations.

  • X: required
  • O: optional
  • ---: Not applicable
keys w/ OpenID Discovery w/o OpenID Discovery Note
provider X X MUST be unique within the file. Loginbuddy uses this value (i.e. 'google') to identify the desired provider
issuer X X MUST be obtained from the provider (This should match the value found within the OpenID Configuration file available via ./well-known/openid-configuration)
openid_configuration_uri X --- Loginbuddy uses this absolute URI instead of concatenating {issuer}/.well-known/openid-configuration
client_id X X MUST be obtained from the provider (Loginbuddy uses this to identity itself at the provider)
client_secret X X MUST be obtained from the provider. (Loginbuddy is a 'confidential' client and presents the client_secret when exchanging the 'code' for a token)
redirect_uri X X MUST point to Loginbuddy, e.g.: 'https://local.loginbuddy.net:8444/callback'. The provider will send responses to this endpoint
response_type O O Loginbuddy supports 'code' and 'id_token'. 'code' is recommended and also the default if not or incorrectly configured
scope O O In most cases this will be 'openid profile email'. This is also the default if it is left empty (This is a [,; ] separated list of values)
authorization_endpoint --- X Absolute URI of the authorization endpoint
pushed_authorization_request_endpoint --- O Absolute URI of the pushed_authorization endpoint. If configured, it will be used
token_endpoint --- X Absolute URI of the token endpoint and is only required for response_type=code
userinfo_endpoint --- O Absolute URI of the userinfo endpoint. If not provided this info is simply not available
jwks_uri --- O Absolute URI of the jwks_uri for id_token (JWT) validations. If not provided id_token are not validated by Loginbuddy
response_mode O O This can take on 'query' or 'form_post'. This can be skipped in most cases, however, 'response_mode=form_post' is required for Sign In with Apple
pkce O O Unfortunately, some providers fail requests if PKCE is used but not supported on their side. Set this to false to prevent Loginbuddy from using PKCE
mappings O O Map claims of a provider response to default claims in Loginbuddy. This is useful for non-OpenID Connect compliant providers (details further down)
dpop_signing_alg O O Choose a value as promoted via dpop_signing_alg_values_supported by the OpenID Provider. If left empty Loginbuddy will choose one (RS256 and ES256 are supported)

TIP: You can also leverage OpenID Connect Dynamic Registration. If the target OpenID Provider supports this specification, you only need to configure these three values for that provider:

  • provider, issuer, openid_configuration_uri

Provider templates

Loginbuddy has built in templates for common providers. If the preferred provider is available as a template only a few values need to be configured in the current configuration.

Templates may be used as shown in this example (using LinkedIn as a provider):

  • check if a template is available:
    • open {loginbuddy}/docker-build/add-ons/templates/configTemplates.json
    • find the provider (linkedin)
    • find the values that are empty (e.g. client_id) and set them in the next step
  • create a provider configuration in config.json
    • "provider":"MyLinkedIn" // choose a name you like. However, to find a MyLinkedIn button that users can select on Loginbuddy's default UI, an image named MyLinkedIn.png must be available. See more info on images further down
    • "template": "linkedin" // choose the value that matches the provider value in configTemplates.json
    • "client_id": "{your-client_id}" // configure the value given by LinkedIn
    • "client_secret": "{your-client_secret}" // configure the value given by LinkedIn
    • "redirect_uri": "{your-redirect_uri-as-configured-at-LinkedIn}" // the redirect_uri that was registered at LinkedIn. To keep things simple during development, register the default value as found in the template (or with :8444 if using the samples project)

The provider configuration now may look as simple as this:

{
  "provider": "MyLinkedIn",
  "template": "linkedin",
  "client_id": "myLinkedInClientid",
  "client_secret": "myLinkedInClientid"
}

That's it, the provider is configured!

Please find more examples in the samples project. Overwrite any value of the template in config.json (including "redirect_uri": "https://local.loginbuddy.net:8444/callback" if you are using the samples project).

2. Configure Clients in Loginbuddy

This is required for inbound connections, as in Loginbuddy receiving requests from clients.

As an OpenID Connect proxy Loginbuddy follows the specifications (OAuth 2.0, OpenID Connect).

The table below shows which values are required for each client to enable communication with Loginbuddy.

All configuration is done in the file config.json. If you are using the samples project this is at ./docker-build/add-ons/loginbuddy/config.json.

The overview below displays the JSON configuration keys within the section clients. This is a JSONArray and may contain multiple configurations.

  • X: required
  • O: optional
  • ---: Not applicable
keys usage Note
client_id X MUST be unique and identifies the client
client_type X MUST specify if your client is 'public' or 'confidential' (see Tip below)
redirect_uris X MUST, an array of redirect_uris! Any of the configured ones may be used during authorization flows
client_uri O Currently not in use. It should be the 'homepage' of the client
client_secret O MUST be specified for client_type=confidential
providers O An array of providers. If specified, this client is only able to use providers listed here. Reference the values of 'provider' within the 'providers' section in this file
accept_dynamic_provider O If set to 'true' OpenID Connect Dynamic Registration is supported for this client. Users will be able to provide their own provider on the fly
signed_response_alg O If set to 'RS256' or 'ES256' Loginbuddy will sign its response as JWT and set the response content-type to application/jwt. Loginbuddys /jwks API contains the required public key to validate the JWT signature
on_behalf_of O An array of OpenID Connect provider issued token types (and desired signing algorithms) that should be re-signed by Loginbuddy. Currently supported: 'id_token'. Loginbuddys /jwks API contains the required public key to validate the id_token signature
client_name O SHOULD be set, it will be displayed to users on the provider selection screen. Default: client_id
tos_uri O The link to the terms and conditions of the client. This is displayed on the provider selection page
policy_uri O The link to the privacy policy of the client. This is displayed on the provider selection page
logo_uri O The link to the clients logo. This is displayed on the provider selection page
contacts O And array of contacts who may be contacted for any client related concerns. There is no default use of this information

Tip: determine the value for client_type:

  • client_type=confidential: use this only if your application is a web application or otherwise able to secure the client_secret!
  • client_type=public: use this for any client that is as open as a book, i.e.: SPA, mobile apps, or other types of JavaScript clients

Notes:

  • signed_response_alg
    • if an unsupported algorithm was configured, Loginbuddy defaults to RS256
  • on_behalf_of:
    • this is useful if your client expects any JWT (currently id_token) to be signed by Loginbuddy since Loginbuddy is the 'issuing authorization server' for it
    • however, the clients JWT signature validation may fail since the OpenID Provider is the issuer (iss == provider) but not Loginbuddy
    • use this configuration to avoid those issues
      • configuration: "on_behalf_of": [{"token_type": "id_token"}] or "on_behalf_of": [{"token_type": "id_token", "alg": "RS256"}] // ES256 is also supported
    • the content of the re-signed id_token contains this object (nonce is always included even if the client did not provide one, Loginbuddy generates that):
    {"on_behalf_of": {"iss":"original_issuer", "aud": "original_aud (the configured client_id to be used against that provider)", "nonce":  "nonce"}}

3. Permissions

This is required for outbound connections, as in Loginbuddy connecting to providers.

As previously mentioned, Loginbuddy runs with an enabled security manager. For that reason it will not connect to any endpoint by default.

To enable endpoints, so called SocketPermissions must be configured. To keep it simple all other required permissions are configured out of the box.

The file is called permissions.policy. If you are using the samples project this is at ./docker-build/add-ons/loginbuddy/permissions.policy.

Tip: for your convenience, the samples project has permissions for google, linkedIn, github and apple pre-configured. They just need to be uncommented!

The file format adheres to the Java policy file format. When Loginbuddy starts up that file is merged to the main existing permissions file. There is nothing else you need to do except for specifying target endpoints.

You should progress as follows:

  • in a browser, connect to the providers OpenID Connect Discovery endpoint and look at the response
  • copy all domains of these endpoints:
    • the domain from which you retrieved the OpenID Discovery document itself
    • token_endpoint
    • userinfo_endpoint
    • jwks_uri

If your provider has no OpenID Connect Discovery endpoint, copy the domains that you specified manually for the providers configuration.

Example: In the case of google that would be these domains (Note, these may change over time!):

  • accounts.google.com, oauth2.googleapis.com, openidconnect.googleapis.com, www.googleapis.com

The matching permissions.policy file looks like this:

grant codeBase "file:${catalina.home}/webapps/ROOT/-" {
    permission java.net.SocketPermission "accounts.google.com", "connect,resolve";
    permission java.net.SocketPermission "oauth2.googleapis.com", "connect,resolve";
    permission java.net.SocketPermission "openidconnect.googleapis.com", "connect,resolve";
    permission java.net.SocketPermission "www.googleapis.com", "connect,resolve";
};  

In addition, of course, by default Loginbuddy will not connect to any endpoint that uses self-signed SSL certificates!

NOTE:

Permission templates for many providers can be found here: {loginbuddy}/docker-build/add-ons/templates/permissionsTemplate.policy

4. OpenID Connect Discovery

This is required for inbound connections, as in Loginbuddy receiving requests from clients.

Loginbuddy provides an OpenID Connect Discovery endpoint but uses the configuration also for itself. For example, it picks up its own issuer value of this configuration.

The file is called discovery.json. If you are using the samples project this is at ./docker-build/add-ons/loginbuddy/discovery.json.

Note: Since Loginbuddy is a proxy some values cannot really be supported. However, some of those will be supported by extending the proxy functionality in the future.

  • X: required
  • O: optional
  • ---: Not applicable - not in use
keys usage Note
issuer X The scheme, domain and port where Loginbuddy is available, i.e. https://local.loginbuddy.net
response_types_supported X MUST be 'code' as only value in a JSON array
grant_types_supported X MUST be 'authorization_code' and optionally 'refresh_token' as only values in a JSON array
authorization_endpoint X MUST be the combination of {issuer}/authorization
token_endpoint_auth_methods_supported X One or both of 'client_secret_post','client_secret_basic', configured in a JSON array. This is relevant for confidential clients
token_endpoint O MUST be the combination of {issuer}/token
scopes_supported O MUST be the ones your client should be able to use. In most cases 'openid email profile' as a JSON array
code_challenge_methods_supported O MUST be 'S256'. Loginbuddy supports PKCE but not algorithm 'NONE'
service_documentation O Provide an endpoint where clients can take a user for further documentations
id_token_signing_alg_values_supported O Loginbuddy is not issuing id_token yet, therefore this is not used
subject_types_supported --- Loginbuddy does not manage users therefore this is not relevant
jwks_uri O An endpoint is provided at {issuer}/jwks and returns the active public JSON Web Keys
userinfo_endpoint O MUST be {issuer}/userinfo. Loginbuddy will proxy a request to the provider that issued the given access_token. NOTE: only within 60s after the access_token was received from the provider
signing_alg_values_supported O An array of algorithms that Loginbuddy supports for responding with a signed JWT. Currently RS256 and ES256 are supported
pushed_authorization_request_endpoint O MUST be {issuer}/pauthorize. The endpoint that supports oauth pushed authorization requests
management O This is a JSON object and contains endpoints to manage Loginbuddy. If not specified, the management APIs are not accessible. See more details in Protocols and APIs

At this point everything necessary is configured. If you have updated the configuration files of the samples project these will be picked up when launching that setup the next time!

Other configurations and more details

This section describes more details and optional configurations.

Provider images

Loginbuddy is generating a simple web site that allows users to select their favourite provider. By default, Loginbuddy will also display a matching image if one was provided. To use custom images do the following:

  • configure a name for your supported provider in config.json
    • ... "provider": "server_loginbuddy" ...
  • download or create an image named {provider}.png, i.e.: server_loginbuddy.png
  • place that image in {loginbuddy}/net.loginbuddy.service/web/images of Loginbuddys directories

Loginbuddy will now pick-up that image the next time it launches.

Mapping of claims

Some providers do not adhere to the OpenID Connect specification when it comes to naming claims in responses of /userinfo. That causes challenges for clients because these now have to match claims to certain values. For example, a provider may use first_name instead of given_name.

Loginbuddy can take care of that problem with simple mappings.

The JSON key providers[x].mappings in the file config.json can be configured to translate a provider-claim to an OpenID Connect-claim.

Loginbuddys response to a client will include an element named details_normalized which contains those normalized values.

This allows the receiving client to always reference the same claims, no matter which provider was chosen by the user!

Generally, a mapping works like this:

  • set mappings.key to loginbuddyResponse.key.value, i.e.: sets details_normalized.given_name to providerResponse.first_name
  • selecting a source value is done using json-path plus some special ingredients
  • Loginbuddy processes the JSON path value at runtime like this:
    • Loginbuddy first assembles its response and applies json-path afterwards to build details_normalized
    • It then attaches details_normalized to the response that is returned to the client

Tip: if you have tried the samples project look at the final response. All elements except for details_normalized may be referenced via json-path to select mapping values!

To get started, here is an example mapping with GitHub. Whatever is shown in the value field references a key in GitHubs userinfo response:

...
"mappings": {
  "target-claim": "source-claim",                           // example        = generally, select a target-claim to be set to a source-claim       
  "name": "$.details_provider.userinfo.name",               // name           = userinfo.name.value
  "given_name": "$.details_provider.userinfo.name:[0]",     // given_name     = userinfo.name.value.split(" ")[0] -- GitHub does not provide a specific value for 'first_name'. We split 'name' by space (' ') and use the first part (index 0)
  "family_name": "$.details_provider.userinfo.name:[1]",    // family_name    = userinfo.name.value.split(" ")[1] -- GitHub does not provide a specific value for 'last_name'. We split 'name' by space (' ') and use the second part (index 1)
  "picture": "",                                            // Github does not provide a pictureURL, nothing to map
  "email": "$.details_provider.userinfo.login",             // email          = userinfo.login.value  -- The field 'email' has always been empty during my tests. Therefore we set the value to 'login' and let the app handle the fact that the value is not an email
  "email_verified": "asis:true",                            // email_verified = true -- GitHub does not provide a value. We tell mapping to use 'true' as-is, no translation
  "provider": "$.details_provider.provider",                // provider       = details_provider.provider
  "sub": "$.details_provider.userinfo.id"                   // sub            = userinfo.id.value
}

That example shows all default mapping rules:

  • $.source-claim: Loginbuddy uses JSON path expressions on the response it is building itself just before it adds details_normalized
  • $.source-claim:[index]: Loginbuddy splits the value by space (' ') which was selected by the source-claim and uses the part found as index (starting by zero)
  • asis:value: we advise Loginbuddy to use value literally, as-is with no translation. Use this for hard coded values

Note: all values are translated to strings! If your application needs other data types, it has to parse the value to that.

To make life simple, the key details_normalized is always included in Loginbuddys token response and supports a default mapping.

Advanced:

Sometimes a special mapping is needed. For example, in the case of LinkedIn, the email address is not included in the userinfo response. A second request is required for that!

Loginbuddy certainly handles this case too. This is how it works, explained by the email claim:

...
"email": {
    "resource": "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))",  // this endpoint returns an email address ...
    "mapping_rule": "$.elements[0].handle~.emailAddress"  // ... which is found in the response using this json-path expression 
    "resource_type": "protected",  // the endpoint is OAuth protected and requires an access_token. If this is not specified Loginbuddy assumes it is an open endpoint
...

At runtime, Loginbuddy uses the access_token that was issued by LinkedIn, connects to the endpoint, applies the json-path expressions and sets the claim email.

Remote Cache

By default Loginbuddy uses a local, built-in cache to manage sessions. This works great but causes problems if Loginbuddy gets restarted.

To mediate that problem Loginbuddy can be configured to use Hazelcast. When configured for Hazelcast Loginbuddy keeps its state in Hazelcast and with that it may be restarted without loosing session data.

These configurations are required to make it work:

In docker-compose configure these environment variables:

  • HAZELCAST=hazelcastServer01,hazelcastServer02
    • if this variable is configured, Loginbuddy tries to connect to these Hazelcast servers
    • if the Hazelcast servers cannot be reached Loginbuddy falls back to its local cache and logs a warning

Add socket permissions in the permissions file:

  • permissions.policy
    • add a socket permission for each configured Hazelcast server
    • e.g.: permission java.net.SocketPermission "hazelcastServer01", "connect,resolve";
    • e.g.: permission java.net.SocketPermission "my.hazelcast.com", "connect,resolve";

Other permissions are pre-configured and enabled automatically when Hazelcast is enabled.

Loginbuddy properties

Loginbuddy uses a properties file for configurations such as authorization_code lifetimes. If you want to configure it, the file is called loginbuddy.properties and has explanatory comments with it. You may also implement a custom configuration loader (i.,e.: leveraging a database) and specify its name here.

Logging

Loginbuddy does not log to files, only to the console. The only exception are access_logs that are generated at:

  • /usr/local/tomcat/log/loginbuddy_access_log*.txt

Configure the log level at:

  • {loginbuddy}/docker-build/add-ons/server/conf/logging.properties

Update .level = INFO to your desired level and rebuild Loginbuddy (or map that file as a volume) then start Loginbuddy.