-
Notifications
You must be signed in to change notification settings - Fork 6
Configuration
Note:
Everything here is only required if you would like to add an OpenID provider to Loginbuddy. For just trying out Loginbuddy it is easier to run through the Quick Start guide!
Loginbuddy requires four items to be configured:
- OpenID Providers: Providers that you want Loginbuddy to support
- Clients: Clients of Loginbuddy (that would be your web application or single-page app (SPA))
- Permissions: Endpoints you want Loginbuddy to connect to (this would be endpoints of supported providers) have to be registered (exception: dynamically registered providers)
- OpenID Connect Discovery: Loginbuddy itself provides a /.well-known/openid-configuration endpoint and needs it for its own configuration
All configuration files are located here:
- {loginbuddy}/net.loginbuddy.service/src/main/resources
- these files are used when building Loginbuddy
Note: Clients and providers are configured within the same file. It is called 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 at the end of this file. It really is not that hard although it may look like it!
Once you got down to item 4 you can try out your configuration by launching Loginbuddy!
I highly recommend that you run and may modify the demo setup first. Doing so will not require a single line of code and you may be done in just a few minutes!
Let us get started!
This is required for outbound connections, as in Loginbuddy connecting to OpenID Providers.
TIP: Whatever you do, always use Loginbuddys demo setup to get started with any provider you want to support. No need to write a single line of code for your first steps!
Before you can configure anything in this section you need to sign up at providers you want to support. For each provider you need to register Loginbuddy as an OAuth client. There are some values you need to share and others you will receive:
You have to share with your OpenID Provider
- redirect_uri: use https://local.loginbuddy.net/callback if you are using the demo setup. Otherwise, provide the value that matches your environment (https://{your-domain}/callback})
- client_type: confidential (most providers categorize this as web application)
- response_type: usually not a question, but, if you get to it, opt-in for response_type=code and grant_type=authorization_code. Sometimes this is also called three-legged flow
- requested features: at each provider, find the location where openid connect gets enabled!
-
client authentication methods: this is even more rarely, but if you are asked how Loginbuddy authenticates itself at the providers token endpoint configure client_secret_post
- this setting is called token_endpoint_auth_methods_supported in OpenID Connect terms
You will receive from your OpenID Provider
- client_id: this is the identifier which Loginbuddy uses to identify itself at the provider
- client_secret: since Loginbuddy is a confidential client it will receive a client_secret which it uses when exchanging an authorization_code for a token
If the provider supports OpenID Connect Discovery you will get:
-
OpenID Configuration endpoint: this endpoint always ends in /.well-known/openid-configuration
- here is the example of googles endpoint: https://accounts.google.com/.well-known/openid-configuration
If OpenID Connect Discovery is not supported you need to find:
- issuer: the value the OpenID Provider uses to identify itself. This is also used by Loginbuddy when validating id_token
- authorization_endpoint: Loginbuddy uses this to initiate the authorization flow
- token_endpoint: Loginbuddy uses this to exchange an authorization_code for a token response
- userinfo_endpoint: Loginbuddy sends a request here to retrieve user details. If this is not available, well, that info will not be retrieved
-
scope: Loginbuddy will use openid email profile if none is specified. However, if your provider has custom values they need to be configured
- if different types of OAuth scopes are available, choose the ones used for social login
-
jwks_uri: Loginbuddy sends a request here to retrieve JSON Web Keys in order to validate id_token
- if this is not available 1.) the provider either does not issue id_token or 2.) Loginbuddy is not able to validate received id_token
This procedure has to be repeated for all providers Loginbuddy should support.
TIP: Check the demo setup for configuration templates! It may simplify your life ({loginbuddy}/docker-build/add-ons/demosetup/config.json). If a template exists, you only have to add your client_id and client_secret, other values are provided!
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 demo setup this is at ./docker-build/add-ons/demosetup.
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/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 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 |
token_endpoint | --- | X | Absolute URI of the token endpoint |
userinfo_endpoint | --- | O | Absolute URI of the userinfo endpoint. If not provided this info if 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) |
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* image, that users can select, 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, register the default value at LinkedIn
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 demosetup. Overwrite any value of the template in config.json.
NOTE: Loginbuddy processes templates and configurations in this order:
- load config.json
- load configTemplate.json
- for each provider in config.json that references a template the template values are overwritten with values found in config.json
- each provider in config.json that does not reference a template is used as configured
- apply default values (i.e.: scope, pkce (true/false))
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 demo setup this is at ./docker-build/add-ons/demosetup.
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. The value is a UUID |
client_type | X | MUST specify if your client is 'public' or 'confidential' (see Tip below) |
redirect_uri | X | MUST point to your application! Loginbuddy will send responses to that endpoint. This is a [,; ] separated list of values |
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' 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 |
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
Note: the value of signed_response_alg is currently ignored. If it is configured Loginbuddy will use RS256.
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 permissions are configured out of the box.
The file is called permissions.policy. If you are using the demo setup this is at ./docker-build/add-ons/demosetup.
Tip: for your convenience, the demo setup has permissions for google, linkedIn, github and apple preconfigured. 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 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, Loginbuddy will not connect to any endpoint that uses self-signed SSL certificates!
NOTE:
Permission templates for all provider templates 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 demo setup this is at ./docker-build/add-ons/demosetup.
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' as only value 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_uri 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 given by the provider |
signing_alg_values_supported | O | An array of algorithms that Loginbuddy supports for responding with a signed JWT. Currently RS256 is the only supported value |
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 demo setup these will be picked up when launching that setup the next time!
This section describes more details and optional configurations.
Some providers do not adhere to the OpenID Connect specification when it comes to naming claims. 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.
This can be compensated 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.: set 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 that element to the response that is returned to the client
Tip: if you have tried the demo setup 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.
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..
-
public client
- with provider restrictions
-
confidential client
- without provider restrictions
-
provider without support for OpenID Connect Discovery
-
provider with support for OpenID Connect Discovery
-
provider with mappings
{ "clients": [{ "client_id": "clientIdForTestingPurposes", "client_type": "public", "client_uri": "https://democlient.loginbuddy.net", "redirect_uri": "https://democlient.loginbuddy.net/callback,https://local.soapui.com/redirect", "providers": ["server_loginbuddy", "google"] }, { "client_id": "clientIdForTestingPurposesConfidential", "client_secret": "myFancyAndSecureClientSecret", "client_type": "confidential", "client_uri": "https://democlient.loginbuddy.net", "redirect_uri": "https://democlient.loginbuddy.net/callback", "accept_dynamic_providers": true, "signed_response_alg": "RS256" } ], "providers": [{ "provider": "server_loginbuddy", "issuer": "https://demoserver.loginbuddy.net", "client_id": "loginbuddy_demoId", "client_secret": "loginbuddy_demoSecret", "authorization_endpoint": "https://demoserver.loginbuddy.net/authorize", "token_endpoint": "https://demoserver.loginbuddy.net/token", "userinfo_endpoint": "https://demoserver.loginbuddy.net/userinfo", "jwks_uri": "https://demoserver.loginbuddy.net/jwks", "scope": "openid profile email", "response_type": "code", "redirect_uri": "https://local.loginbuddy.net/callback" }, { "provider": "google", "issuer": "https://accounts.google.com", "openid_configuration_uri": "https://accounts.google.com/.well-known/openid-configuration", "client_id": "clientIdFromGoogle", "client_secret": "clientSecretFromGoogle", "response_type": "code", "redirect_uri": "https://local.loginbuddy.net/callback" }, { "provider": "linkedin", "issuer": "https://www.linkedin.com", "client_id": "", "client_secret": "", "authorization_endpoint": "https://www.linkedin.com/oauth/v2/authorization", "token_endpoint": "https://www.linkedin.com/oauth/v2/accessToken", "userinfo_endpoint": "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,profilePicture(displayImage~:playableStreams))", "response_type": "code", "scope": "r_liteprofile r_emailaddress", "redirect_uri": "https://local.loginbuddy.net/callback", "pkce": false, "mappings": { "sub": "$.details_provider.userinfo.id", "given_name": "$.details_provider.userinfo.localizedFirstName", "family_name": "$.details_provider.userinfo.localizedLastName", "picture": "$.details_provider.userinfo.profilePicture.displayImage~.elements[0].identifiers[0].identifier", "email": { "resource": "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))", "resource_type": "protected", "mapping_rule": "$.elements[0].handle~.emailAddress" }, "email_verified": "asis:true", "provider": "$.details_provider.provider" } } ] }
-
Not all keys are listed in this example. Check {loginbuddy}/apitest/docker/discovery.json for a complete example.
{ "issuer": "https://{your-domain}", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "token_endpoint_auth_methods_supported": ["client_secret_post","client_secret_basic"], "scopes_supported": ["openid","email","profile"], "authorization_endpoint": "https://{your-domain}/authorize", "token_endpoint": "https://{your-domain}/token", "service_documentation": "https://github.com/SaschaZeGerman/loginbuddy", "code_challenge_methods_supported": ["S256"], "signing_alg_values_supported": ["RS256"], "pushed_authorization_request_endpoint": "https://{your-domain}/pauthorize" }
-
support for demoserver.loginbuddy.net, loginbuddy-oidcdr, google, GitHub
grant codeBase "file:${catalina.home}/webapps/ROOT/-" { permission java.net.SocketPermission "demoserver.loginbuddy.net", "connect,resolve"; permission java.net.SocketPermission "loginbuddy-oidcdr", "connect,resolve"; 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"; permission java.net.SocketPermission "github.com", "connect,resolve"; permission java.net.SocketPermission "api.github.com", "connect,resolve"; };
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 enable the 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 it launches.
{
"access_token": "provider-access-token",
"refresh_token": "provider-refresh-token",
"scope": "provider-granted-scope",
"expires_in": 3600,
"token_type": "Bearer",
"id_token": "provider-id-token",
"details_provider": {
"id_token_payload": {
"all-claims": "content-of-validated-id-token" // empty if no id_token was issued or if the validation failed
},
"userinfo": {
"all-claims": "content-of-userinfo-response"
},
"provider": "chosen-provider"
},
"details_loginbuddy":{
"iss": "loginbuddy-instance-as-cofigured-in-discovery.json",
"nonce": "given-nonce", // as given by your client. A login generated value otherwise
"iat": 1234567890,
"aud": "given-client-id"
},
"details_normalized": {
"name": "...",
"given_name": "...",
"family_name": "...",
"picture": "...",
"email": "...",
"email_verified": "...",
"provider": "...",
"sub": "..."
}
}
For more details on how to map files via volumes, how to configure config.json, discovery.json, permissions.json check out the demo configuration found at:
- ./docker-build/add-ons/demosetup