// Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package oidc import ( "encoding/json" "maps" "net/url" "slices" "strings" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/identity" "github.com/ory/x/urlx" ) const ( // AccountLinkingModeVerifyWithExistingCredential requires the user to verify their identity // with an existing credential before accounts are linked. AccountLinkingModeVerifyWithExistingCredential = "confirm_with_existing_credential" // AccountLinkingModeAutomatic silently links accounts if the provider verifies email ownership. AccountLinkingModeAutomatic = "automatic" // ProviderTypeApple is the provider type for Sign in with Apple. ProviderTypeApple = "apple" // ProviderTypeGoogle is the provider type for Sign in with Google. ProviderTypeGoogle = "google" // UpdateIdentityOnLoginNever disables identity updates on login (default). UpdateIdentityOnLoginNever = "never" // UpdateIdentityOnLoginAutomatic re-runs the Jsonnet claims mapper on every // OIDC login and updates the identity's traits and metadata automatically. UpdateIdentityOnLoginAutomatic = "automatic" ) type Configuration struct { // ID is the provider's ID ID string `json:"id"` // Provider is either "generic" for a generic OAuth 2.0 / OpenID Connect Provider or one of: // - generic // - google // - github // - github-app // - gitlab // - microsoft // - discord // - salesforce // - slack // - facebook // - auth0 // - vk // - yandex // - apple // - spotify // - netid // - dingtalk // - linkedin // - patreon // - amazon // - uaepass Provider string `json:"provider"` // Label represents an optional label which can be used in the UI generation. Label string `json:"label"` // ClientID is the application's Client ID. ClientID string `json:"client_id"` // ClientSecret is the application's secret. ClientSecret string `json:"client_secret"` // IssuerURL is the OpenID Connect Server URL. You can leave this empty if `provider` is not set to `generic`. // If set, neither `auth_url` nor `token_url` are required. IssuerURL string `json:"issuer_url"` // AuthURL is the authorize url, typically something like: https://example.org/oauth2/auth // Should only be used when the OAuth2 / OpenID Connect server is not supporting OpenID Connect Discovery and when // `provider` is set to `generic`. AuthURL string `json:"auth_url"` // TokenURL is the token url, typically something like: https://example.org/oauth2/token // Should only be used when the OAuth2 / OpenID Connect server is not supporting OpenID Connect Discovery and when // `provider` is set to `generic`. TokenURL string `json:"token_url"` // Tenant is the Azure AD Tenant to use for authentication, and must be set when `provider` is set to `microsoft`. // Can be either `common`, `organizations`, `consumers` for a multitenant application or a specific tenant like // `8eaef023-2b34-4da1-9baa-8bc8c9d6a490` or `contoso.onmicrosoft.com`. Tenant string `json:"microsoft_tenant"` // SubjectSource is a flag which controls from which endpoint the subject identifier is taken by microsoft provider. // Can be either `userinfo` or `me` or `oid`. // If the value is `userinfo` then the subject identifier is taken from sub field of userinfo standard endpoint response. // If the value is `me` then the `id` field of https://graph.microsoft.com/v1.0/me response is taken as subject. // If the value is `oid` then the the oid (Object ID) is taken to identify users across different services. // The default is `userinfo`. SubjectSource string `json:"subject_source"` // TeamId is the Apple Developer Team ID that's needed for the `apple` `provider` to work. // It can be found Apple Developer website and combined with `apple_private_key` and `apple_private_key_id` // is used to generate `client_secret` TeamId string `json:"apple_team_id"` // PrivateKeyId is the private Apple key identifier. Keys can be generated via developer.apple.com. // This key should be generated with the `Sign In with Apple` option checked. // This is needed when `provider` is set to `apple` PrivateKeyId string `json:"apple_private_key_id"` // PrivateKeyId is the Apple private key identifier that can be downloaded during key generation. // This is needed when `provider` is set to `apple` PrivateKey string `json:"apple_private_key"` // Scope specifies optional requested permissions. Scope []string `json:"scope"` // Mapper specifies the JSONNet code snippet which uses the OpenID Connect Provider's data (e.g. GitHub or Google // profile information) to hydrate the identity's data. // // It can be either a URL (file://, http(s)://, base64://) or an inline JSONNet code snippet. Mapper string `json:"mapper_url"` // RequestedClaims is a string encoded json object that specifies claims and optionally their properties that should be // included in the id_token or returned from the UserInfo Endpoint. // // More information: https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter RequestedClaims json.RawMessage `json:"requested_claims"` // An optional organization ID that this provider belongs to. // This parameter is only effective in the Ory Network. OrganizationID string `json:"organization_id"` // AdditionalIDTokenAudiences is a list of additional audiences allowed in the ID Token. // This is only relevant in OIDC flows that submit an IDToken instead of using the callback from the OIDC provider. AdditionalIDTokenAudiences []string `json:"additional_id_token_audiences"` // ClaimsSource is a flag which controls where the claims are taken from when // using the generic provider. Can be either `userinfo` (calls the userinfo // endpoint to get the claims) or `id_token` (takes the claims from the id // token). It defaults to `id_token`. ClaimsSource string `json:"claims_source"` // PKCE controls if the OpenID Connect OAuth2 flow should use PKCE (Proof Key for Code Exchange). // Possible values are: `auto` (default), `never`, `force`. // - `auto`: PKCE is used if the provider supports it. Requires setting `issuer_url`. // - `never`: Disable PKCE entirely for this provider, even if the provider advertises support for it. // - `force`: Always use PKCE, even if the provider does not advertise support for it. OAuth2 flows will fail if the provider does not support PKCE. // IMPORTANT: If you set this to `force`, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. // Instead of /self-service/methods/oidc/callback/, you must use /self-service/methods/oidc/callback // (Note the missing path segment and no trailing slash). PKCE string `json:"pkce"` // FedCMConfigURL is the URL to the FedCM IdP configuration file. // This is only effective in the Ory Network. FedCMConfigURL string `json:"fedcm_config_url"` // NetIDTokenOriginHeader contains the orgin header to be used when exchanging a // NetID FedCM token for an ID token. NetIDTokenOriginHeader string `json:"net_id_token_origin_header"` // AccountLinkingMode controls how account conflicts are resolved for this provider. // Possible values: "confirm_with_existing_credential" (default), "automatic". // This is only effective in the Ory Network. AccountLinkingMode string `json:"account_linking_mode,omitempty"` // UpdateIdentityOnLogin controls whether the identity is updated from // OIDC claims on each login. // // Possible values: // - "never" (default): the identity is not updated on login. // - "automatic": re-runs the Jsonnet claims mapper on every OIDC login // and updates the identity's traits and metadata automatically. UpdateIdentityOnLogin string `json:"update_identity_on_login,omitempty"` // AAL2ACRValues lists upstream OIDC `acr` claim values that elevate // the resulting Kratos session to AAL2. When empty, the session AAL // is always AAL1 regardless of the upstream `acr` claim. AAL2ACRValues []string `json:"aal2_acr_values,omitempty"` // AAL2AMRValues lists upstream OIDC `amr` claim values that elevate // the resulting Kratos session to AAL2 when any of them appears in // the upstream `amr` array. When empty, `amr` does not influence the // session AAL. AAL2AMRValues []string `json:"aal2_amr_values,omitempty"` } // AALForClaims returns the session AuthenticatorAssuranceLevel that the // upstream OIDC response should be mapped to, based on the provider's // AAL2ACRValues / AAL2AMRValues allowlists and the ACR / AMR claims // reported by the upstream provider. // // The default is AAL1. AAL2 is only returned when either the upstream // `acr` claim matches one of the configured AAL2ACRValues, or at least // one of the upstream `amr` entries matches one of the configured // AAL2AMRValues. An empty `acr` never matches, even if the allowlist // contains an empty string. func (c *Configuration) AALForClaims(claims *Claims) identity.AuthenticatorAssuranceLevel { if claims == nil { return identity.AuthenticatorAssuranceLevel1 } if claims.ACR != "" && slices.Contains(c.AAL2ACRValues, claims.ACR) { return identity.AuthenticatorAssuranceLevel2 } for _, want := range c.AAL2AMRValues { if slices.Contains(claims.AMR, want) { return identity.AuthenticatorAssuranceLevel2 } } return identity.AuthenticatorAssuranceLevel1 } func (p Configuration) Redir(public *url.URL) string { if p.PKCE == "force" { return urlx.AppendPaths(public, RouteCallbackGeneric).String() } if p.OrganizationID != "" { route := RouteOrganizationCallback route = strings.Replace(route, "{provider}", p.ID, 1) route = strings.Replace(route, "{organization}", p.OrganizationID, 1) return urlx.AppendPaths(public, route).String() } return urlx.AppendPaths(public, strings.Replace(RouteCallback, "{provider}", p.ID, 1)).String() } type ConfigurationCollection struct { BaseRedirectURI string `json:"base_redirect_uri"` Providers []Configuration `json:"providers"` } // !!! WARNING !!! // // If you add a provider here, please also add a test to // provider_private_net_test.go var supportedProviders = map[string]func(config *Configuration, reg Dependencies) Provider{ "generic": NewProviderGenericOIDC, "google": NewProviderGoogle, "github": NewProviderGitHub, "github-app": NewProviderGitHubApp, "gitlab": NewProviderGitLab, "microsoft": NewProviderMicrosoft, "discord": NewProviderDiscord, "salesforce": NewProviderSalesforce, "slack": NewProviderSlack, "facebook": NewProviderFacebook, "auth0": NewProviderAuth0, "vk": NewProviderVK, "yandex": NewProviderYandex, "apple": NewProviderApple, "spotify": NewProviderSpotify, "netid": NewProviderNetID, "dingtalk": NewProviderDingTalk, "linkedin": NewProviderLinkedIn, "linkedin_v2": NewProviderLinkedInV2, "patreon": NewProviderPatreon, "lark": NewProviderLark, "x": NewProviderX, "line": NewProviderLineV21, "jackson": NewProviderJackson, "fedcm-test": NewProviderTestFedcm, "amazon": NewProviderAmazon, "uaepass": NewProviderUAEPass, } func (c ConfigurationCollection) Provider(id string, reg Dependencies) (Provider, error) { for _, p := range c.Providers { if p.ID == id { if f, ok := supportedProviders[p.Provider]; ok { return f(&p, reg), nil } return nil, errors.Errorf("provider type %s is not supported, supported are: %v", p.Provider, maps.Keys(supportedProviders)) } } return nil, errors.WithStack(herodot.ErrNotFound().WithReasonf(`OpenID Connect Provider "%s" is unknown or has not been configured`, id)) }