-
Notifications
You must be signed in to change notification settings - Fork 6
Configuration
Note: Anything here is only required if you would like to add an OpenID Provider to Loginbuddy. For just trying out Loginbuddy, please follow the Quick Start guide!
Loginbuddy acts as an OpenID Provider (provider) proxy. Loginbuddy also tries to validate a request as strict as possible in order to reduce the number of invalid requests that are being send to providers. It is also built to be secure and therefore only supports https and runs with a security manager. By default, Loginbuddy will not connect to any endpoint whatsoever.
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
Note: Clients and providers are configured within the same file. It is called config.json and has this structure:
{"loginbuddy": {"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 modify the demo setup. 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
-
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.
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 loginbuddy.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 | Loginbuddy uses this value (i.e. 'google') to identify the desired provider. MUST be unique within the file |
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 | --- | X | Absolute URI of the userinfo endpoint |
jwks_uri | --- | X | Absolute URI of the jwks_uri for id_token (JWT) validations |
response_mode | O | O | This can take on 'query' or 'from_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
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 loginbuddy/clients.
- X: required
- O: optional
- ---: Not applicable
keys | usage | Note |
---|---|---|
client_id | X | The value your application uses to identify itself (typically 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, any other types of JavaScript clients
Note: the value of signed_response_alg is currently ignored. If a value is configured Loginbuddy will use RS256.
This is required for outbound connections, as in Loginbuddy connecting to providers.
As previously mentioned, Loginbuddy runs with a security manager enabled. 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.
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 certificates (by default)!
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 | C | Loginbuddy is not issuing id_token yet, therefore this is not used |
subject_types_supported | --- | Loginbuddy does not manage users therefor this is not relevant |
jwks_uri | O | An endpoint is provided at {issuer}/jwks_uri and returns the active public JSON Wek Keys |
userinfo_endpoint | O | Loginbuddy will forward a request to the provider that issued the given access_token. NOTE: only right 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 |
pushed_authorization_request_endpoint | O | The endpoint that supports oauth pushed authorization requests. This is the combination of {issuer}/pauthorize |
At this point in time you can stop and launch the demo setup using your updated configurations (assuming you have modified the demo setup)!
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 problems 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 loginbuddy.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 ingredient. This gets evaluated by Loginbuddy at runtime
- 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. There is no need to modify this file by default. If you want to configure it, the file is called loginbuddy.properties.
-
public client
- with provider restrictions
-
confidential client
- without provider restrictions
-
provider without support for OpenID Connect Discovery
-
provider with support for OpenID Connect Discovery
{ "loginbuddy": { "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" } } ] } }
-
Minimal, only values that are actually supported by Loginbuddy
{ "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 /web/images of Loginbuddys directories
Loginbuddy will now pick-up that image the next time the web site is shown (after a restart).
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