-
Notifications
You must be signed in to change notification settings - Fork 939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: third party custom providers #1984
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- New configuration option `third_party.custom_providers`. `custom_providers` is a map of arbitrarily chosen keys to a `CustomThirdPartyProvider` - this is implemented as a new type differing from the existing configuration type `ThirdPartyProvider` used for built-in providers because they have different configuration requirements. - Both `ThirdPartyProvider` and `CustomThirdPartyProvider` types get a non- configurable, automatically populated `Name` (during the config's `PostProcess`) that sort of serves as an identifier/slug for the provider in order to distinguish provider types at runtime. - A `CustomThirdPartyProvider`s `Name` is automatically prefixed during `PostProcess` with a "custom_" prefix to ensure that providers can be distinguished at runtime. - A (built-in) `ThirdPartyProvider`s `Name` is "hard-coded" through the `DefaultConfig`. - Built-in OAuth/OIDC provider implementations are currently instantiated on-demand instead of once at appliation startup (i.e. unlike SAML providers) - i.e. when a user requests auth/authz with a third party provider, only then a provider is instantiated and created via factory function (`thirdparty.GetProvider`). Custom providers follow this pattern, hence the factory function had to be adjusted to take into account providers with the aforementioned "custom_" prefix (i.e.: if it is a "custom_" provider, instantiate a `customProvider` implementation). - The `customProvider` implementation uses the `go-oidc` library. Instances of providers of the type this library offers can be instantiated by passing in an `issuer` URL. Such an instantiation automatically attempts to retrieve an OIDC discovery document from a `.well-known` endpoint. This also performs an issuer validation. Providers configured to not use OIDC discovery (i.e. `use_discovery` in the `CustomThirdPartyProvider` is `false` or omitted) do not do this issuer check. - The `customProvider` implementation is further based on the assumption that provider user data is only extracted from a userinfo endpoint response, i.e. in case of an OIDC provider, the implementation does not make use of the ID token - no validation is performed on the ID token. - The `customProvider` implementation requires configuring a list of `scopes`: because the custom providers allow configuring both OAuth as well as OIDC providers, we cannot simply set a default set of scopes, e.g. `openid`, which is a required claim for OIDC - some providers return errors on unknown claims so setting this would make the third party auth process prone to errors. - The `customProvider` implementation allows for a simple mapping of claims contained in the userinfo response from the provider to "known" standard OIDC conformant claims at the Hanko backend (defined in the `thirdparty.Claims` struct) through an `attribute_mapping` configuration option. The mapping is a simple one-to-one mapping, i.e. no complex mapping instructions are possible, e.g. mapping concatenations of multiple claims in the provider data source or similar. Any other non-standard claims returned by the provider are placed in a `custom_claims` attribute. Except for the user ID (`sub`), `email` and `email_verified` claims the third party functionality currently does not allow accessing this user data but there's a good chance this will change in the future, so I tried to make sure that any info retrieved from the provider is somehow preserved (it is persisted in the `data` column for an `identity` btw. and updated on every login with the provider). - I also noticed that the `thirdparty.Claims` were missing the `address` claim, so I added that for completeness' sake. - The changes also fix a "bug" in the account `linking` logic whereby third party connections were established by simply assuming that the email retrieved from the provider was verified. So, even if the email address at the provider was not verified (or the provider simply did/does not provide info about the verification status) an account was created and/or linked and the flow API capabilities of automatically triggering a passcode if the backend was configured to require email verification would not take effect. This was a wrong assumption and the verification status is now based on the actual value retrieved from the provider. - In case of a triggered passcode, the changes also modify the token exchange action to prevent showing a `back` button/link, since it does not make sense to go `back` to anything right after the token exchange - there is nothing to go "back" to.
bjoern-m
reviewed
Nov 29, 2024
Co-authored-by: bjoern-m <[email protected]> Co-authored-by: Frederic Jahn <[email protected]>
bjoern-m
approved these changes
Dec 3, 2024
FreddyDevelop
approved these changes
Dec 4, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description
These changes enable configuration and usage of custom third party OAuth/OIDC providers.
Implementation
New configuration option
third_party.custom_providers
.custom_providers
is a map of arbitrarily chosen keys to aCustomThirdPartyProvider
- this is implemented as a new type differing from the existing configuration typeThirdPartyProvider
used for built-in providers because they have different configuration requirements.Both
ThirdPartyProvider
andCustomThirdPartyProvider
types get a non- configurable, automatically populatedName
(during the config'sPostProcess
) that sort of serves as an identifier/slug for the provider in order to distinguish provider types at runtime.CustomThirdPartyProvider
sName
is automatically prefixed duringPostProcess
with acustom_
prefix to ensure that providers can be distinguished at runtime.ThirdPartyProvider
sName
is "hard-coded" through theDefaultConfig
.Built-in OAuth/OIDC provider implementations are currently instantiated on-demand instead of once at application startup (i.e. unlike SAML providers) - i.e. when a user requests auth/authz with a third party provider, only then a provider is instantiated and created via factory function (
thirdparty.GetProvider
). Custom providers follow this pattern, hence the factory function had to be adjusted to take into account providers with the aforementionedcustom_
prefix (i.e.: if it is acustom_
provider, instantiate acustomProvider
implementation).The
customProvider
implementation uses thego-oidc
library. Instances of providers of the type this library offers can be instantiated by passing in anissuer
URL. Such an instantiation automatically attempts to retrieve an OIDC discovery document from a.well-known
endpoint. This also performs an issuer validation. Providers configured to not use OIDC discovery (i.e.use_discovery
in theCustomThirdPartyProvider
isfalse
or omitted) "directly" instantiate a provider struct and hence do not do this issuer check (it is only done in the constructor function).The
customProvider
implementation is further based on the assumption that provider user data is only extracted from a userinfo endpoint response, i.e. in case of an OIDC provider, the implementation does not make use of the ID token - no validation is performed on the ID token.The
customProvider
implementation requires configuring a list ofscopes
: because the custom providers allow configuring both OAuth as well as OIDC providers, we cannot simply set a default set of scopes, e.g.openid
, which is a required claim for OIDC - some providers return errors on unknown claims so setting this would make the third party auth process prone to errors.The
customProvider
implementation allows for a simple mapping of claims contained in the userinfo response from the provider to "known" standard OIDC conformant claims at the Hanko backend (defined in thethirdparty.Claims
struct) through anattribute_mapping
configuration option. The mapping is a simple one-to-one mapping, i.e. no complex mapping instructions are possible, e.g. mapping concatenations of multiple claims in the provider data source or similar. Any other non-standard claims returned by the provider are placed in acustom_claims
attribute. Except for the user ID (sub
),email
andemail_verified
claims the third party functionality currently does not allow accessing this user data but there's a good chance this will change in the future, so I tried to make sure that any info retrieved from the provider is somehow preserved (it is persisted in thedata
column for anidentity
btw. and updated on every login with the provider).thirdparty.Claims
were missing theaddress
claim, so I added that for completeness' sake.The changes also fix a bug in the account
linking
logic whereby third party connections were established by simply assuming that the email retrieved from the provider was verified. So, even if the email address at the provider was not verified (or the provider simply did/does not provide info about the verification status), an account was created and/or linked and the flow API capabilities of automatically triggering a passcode if the backend was configured to require email verification would not take effect. This was a wrong assumption and the verification status is now based on the actual value retrieved from the provider.In case of a triggered passcode, the changes also modify the token exchange action to prevent showing a
back
button/link, since it does not make sense to goback
to anything right after the token exchange - there is nothing to go "back" to.How to test
Download Keycloak realm file: hanko-realm.json
Start Keycloak (change path to realm file accordingly):
You can log in to the admin console at http://localhost:8090 - Username:
admin
, PW:admin
. There is one realm(
hanko
) with two clients (hanko
,hanko2
) configured. To view these, select thehanko
realm at the top left, thenselect
clients
in the left sidebar . Selecthanko
orhanko2
from the table.Client
hanko
returnsemail
,profile
claims, a hardcoded custom claimtest_claim
that should map to thecustom_claims
attribute since it is a non- standard claim (view thedata
column after signup/signin - more on the.latter in the next steps) and a custom username claim (
test_username
) that you can use to test the attributemapping (e.g. to map it into the standard claim
preferred_username
). Configure the provider for this client in thebackend/config/config.yaml
:Alternatively, do not use discovery and configure the endpoints explicitly:
Client
hanko2
uses default settings, should also returnemail
andprofile
information but also includes the address (which was added to the HankoClaims
struct, see implementation description above). To request the address and see that it works, also request theaddress
scope:Start the backend and one of the
frontend/examples
. Log in with the provider. Thehanko
realm has one user: Username:test
, PW:test
.Verify sign-up/sign-in/linking works. View the
identites.data
column in the DB for claims.In order to understand assumptions made regarding required/optional attributes of a
custom_provider
: view the validation logic in theconfig.go
file and try to generate the schema (via cmdschema generate config
) and observe its effects on config file validation in your IDE (schema for the config must be configured accordingly).