-
Notifications
You must be signed in to change notification settings - Fork 6
Configuration
Loginbuddy's configuration is file based. Here is an overview:
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!
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
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).
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
- configuration:
- 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"}}
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
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!
This section describes more details and optional configurations.
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.
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.
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 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.
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.