diff --git a/crates/handlers/src/upstream_oauth2/template.rs b/crates/handlers/src/upstream_oauth2/template.rs index 1634948a0..13e2fbf43 100644 --- a/crates/handlers/src/upstream_oauth2/template.rs +++ b/crates/handlers/src/upstream_oauth2/template.rs @@ -15,9 +15,9 @@ use minijinja::{ /// Context passed to the attribute mapping template /// /// The variables available in the template are: -/// - `user`: claims for the user, currently from the ID token. Later, we'll -/// also allow importing from the userinfo endpoint +/// - `user`: claims for the user, merged from the ID token and userinfo endpoint /// - `id_token_claims`: claims from the ID token +/// - `userinfo_claims`: claims from the userinfo endpoint /// - `extra_callback_parameters`: extra parameters passed to the callback #[derive(Debug, Default)] pub(crate) struct AttributeMappingContext { diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 2c7c3e6bc..2aa4f6eec 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -538,7 +538,8 @@ upstream_oauth2: # The issuer URL, which will be used to discover the provider's configuration. # If discovery is enabled, this *must* exactly match the `issuer` field # advertised in `/.well-known/openid-configuration`. - issuer: https://example.com/ + # It must be set if OIDC discovery is enabled (which is the default). + #issuer: https://example.com/ # A human-readable name for the provider, # which will be displayed on the login page @@ -569,8 +570,19 @@ upstream_oauth2: # - `client_secret_post` # - `client_secret_jwt` # - `private_key_jwt` (using the keys defined in the `secrets.keys` section) + # - `sign_in_with_apple` (a special authentication method for Sign-in with Apple) token_endpoint_auth_method: client_secret_post + # Additional paramaters for the `sign_in_with_apple` authentication method + # See https://www.oauth.com/oauth2-servers/pkce/authorization-code-flow-with-pkce/ + #sign_in_with_apple: + # private_key: | + # -----BEGIN PRIVATE KEY----- + # ... + # -----END PRIVATE KEY----- + # team_id: "" + # key_id: "" + # Which signing algorithm to use to sign the authentication request when using # the `private_key_jwt` or the `client_secret_jwt` authentication methods #token_endpoint_auth_signing_alg: RS256 @@ -595,6 +607,19 @@ upstream_oauth2: # - `never`: never use PKCE #pkce_method: auto + # Whether to fetch user claims from the userinfo endpoint + # This is disabled by default, as most providers will return the necessary + # claims in the `id_token` + #fetch_userinfo: true + + # If set, ask for a signed response on the userinfo endpoint, and validate + # the response uses the given algorithm + #userinfo_endpoint_auth_signing_alg: RS256 + + # The userinfo endpoint + # This takes precedence over the discovery mechanism + #userinfo_endpoint: https://example.com/oauth2/userinfo + # The provider authorization endpoint # This takes precedence over the discovery mechanism #authorization_endpoint: https://example.com/oauth2/authorize @@ -607,6 +632,10 @@ upstream_oauth2: # This takes precedence over the discovery mechanism #jwks_uri: https://example.com/oauth2/keys + # Additional parameters to include in the authorization request + #additional_authorization_parameters: + # foo: "bar" + # How user attributes should be mapped # # Most of those attributes have two main properties: @@ -617,7 +646,8 @@ upstream_oauth2: # - `require`: always import the attribute, and fail if it's missing # - `template`: a Jinja2 template used to generate the value. In this template, # the `user` variable is available, which contains the user's attributes - # retrieved from the `id_token` given by the upstream provider. + # retrieved from the `id_token` given by the upstream provider and/or through + # the userinfo endpoint. # # Each attribute has a default template which follows the well-known OIDC claims. # @@ -654,6 +684,11 @@ upstream_oauth2: # - `always`: mark the email address as verified # - `never`: mark the email address as not verified #set_email_verification: import + + # An account name, for display purposes only + # This helps end user identify what account they are using + account_name: + #template: "@{{ user.preferred_username }}" ``` ## `experimental` diff --git a/docs/setup/sso.md b/docs/setup/sso.md index 770b46db5..b80333270 100644 --- a/docs/setup/sso.md +++ b/docs/setup/sso.md @@ -40,6 +40,7 @@ The authentication service supports importing the following user attributes from - The localpart/username (e.g. `@localpart:example.com`) - The display name - An email address + - An account name, to help end users identify what account they are using For each of those attributes, administrators can configure a mapping using the claims provided by the upstream provider. They can also configure what should be done for each of those attributes. It can either: @@ -49,12 +50,20 @@ They can also configure what should be done for each of those attributes. It can - `force`: automatically import the attribute, but don't fail if it is not provided by the provider - `require`: automatically import the attribute, and fail if it is not provided by the provider -A Jinja2 template is used as mapping for each attribute. The template currently has one `user` variable, which is an object with the claims got through the `id_token` given by the provider. +A Jinja2 template is used as mapping for each attribute. The following default templates are used: - `localpart`: `{{ user.preferred_username }}` - `displayname`: `{{ user.name }}` - `email`: `{{ user.email }}` + - `account_name`: none + +The template has the following variables available: + + - `id_token_claims`: an object with the claims got through the `id_token` given by the provider, if provided by the provider + - `userinfo_claims`: an object with the claims got through the `userinfo` endpoint, if `fetch_userinfo` is enabled + - `user`: an object which contains the claims from both the `id_token` and the `userinfo` endpoint + - `extra_callback_parameters`: an object with the additional parameters the provider sent to the redirect URL ## Multiple providers behaviour @@ -95,10 +104,14 @@ upstream_oauth2: # SiWA passes down the user infos as query parameters in the callback # which is available in the extra_callback_parameters variable template: | - {%- set user = extra_callback_parameters["user"] | from_json -%} - {{- user.name.firstName }} {{ user.name.lastName -}} + {%- set u = extra_callback_parameters["user"] | from_json -%} + {{- u.name.firstName }} {{ u.name.lastName -}} email: action: suggest + account_name: + template: | + {%- set u = extra_callback_parameters["user"] | from_json -%} + {{- u.name.firstName }} {{ u.name.lastName -}} ``` ### Authelia @@ -230,6 +243,8 @@ upstream_oauth2: action: suggest template: "{{ user.email }}" set_email_verification: always + account_name: + template: "{{ user.name }}" ``` @@ -261,6 +276,51 @@ upstream_oauth2: email: action: suggest template: "{{ user.email }}" + account_name: + template: "@{{ user.preferred_username }}" +``` + +### GitHub + +GitHub doesn't support OpenID Connect, but it does support OAuth 2.0. +It will use the `fetch_userinfo` option with a manual `userinfo_endpoint` to fetch the user's profile through the GitHub API. + +1. Create a [new application](https://github.com/settings/applications/new). +2. Fill in the form with an application name and homepage URL. +3. Use the following Authorization callback URL: `https:///upstream/callback/` +4. Retrieve the Client ID +5. Generate a Client Secret and copy it + +Authentication service configuration: + +```yaml +upstream_oauth2: + providers: + - id: "01HFS67GJ145HCM9ZASYS9DC3J" + human_name: GitHub + brand_name: github + discovery_mode: disabled + fetch_userinfo: true + token_endpoint_auth_method: "client_secret_post" + client_id: "" # TO BE FILLED + client_secret: "" # TO BE FILLED + authorization_endpoint: "https://github.com/login/oauth/authorize" + token_endpoint: "https://github.com/login/oauth/access_token" + userinfo_endpoint: "https://api.github.com/user" + scope: "read:user" + claims_imports: + subject: + template: "{{ userinfo_claims.id }}" + displayname: + action: suggest + template: "{{`{{ userinfo_claims.name }}" + localpart: + action: ignore + email: + action: suggest + template: "{{ userinfo_claims.email }}" + account_name: + template: "@{{ userinfo_claims.login }}" ``` @@ -291,6 +351,8 @@ upstream_oauth2: email: action: suggest template: "{{ user.email }}" + account_name: + template: "{{ user.email }}" ``` @@ -384,4 +446,6 @@ upstream_oauth2: action: suggest template: "{{ user.email }}" set_email_verification: always + account_name: + template: "{{ user.preferred_username }}" ```