diff --git a/cmd/start/start.go b/cmd/start/start.go index e424a3d90..c89f4361e 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -425,8 +425,6 @@ func startAPIs( assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge) apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle)) - apis.RegisterHandlerOnPrefix(idp.HandlerPrefix, idp.NewHandler(commands, queries, keys.IDPConfig, config.ExternalSecure, instanceInterceptor.Handler)) - userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator(), config.ExternalSecure, login.EndpointResources, login.EndpointExternalLoginCallbackFormPost, login.EndpointSAMLACS) if err != nil { return nil, err @@ -490,6 +488,8 @@ func startAPIs( apis.RegisterHandlerOnPrefix(login.HandlerPrefix, l.Handler()) apis.HandleFunc(login.EndpointDeviceAuth, login.RedirectDeviceAuthToPrefix) + apis.RegisterHandlerOnPrefix(idp.HandlerPrefix, idp.NewHandler(commands, queries, keys.IDPConfig, config.ExternalSecure, instanceInterceptor.Handler, login.IDPCallbackRedirect)) + // After OIDC provider so that the callback endpoint can be used if err := apis.RegisterService(ctx, oidc_v2.CreateServer(commands, queries, oidcServer, config.ExternalSecure)); err != nil { return nil, err diff --git a/console/src/app/modules/providers/provider-next/provider-next.service.ts b/console/src/app/modules/providers/provider-next/provider-next.service.ts index 245571e69..8b0ed0406 100644 --- a/console/src/app/modules/providers/provider-next/provider-next.service.ts +++ b/console/src/app/modules/providers/provider-next/provider-next.service.ts @@ -85,7 +85,7 @@ export class ProviderNextService { map((env) => [ { label: 'ZITADEL Callback URL', - url: `${env.issuer}/ui/login/login/externalidp/callback`, + url: `${env.issuer}/idps/callback`, }, ]), ); diff --git a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts index b0741c7b3..eede73dc8 100644 --- a/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts +++ b/console/src/app/modules/providers/provider-saml-sp/provider-saml-sp.component.ts @@ -84,11 +84,7 @@ export class ProviderSamlSpComponent { downloadable: true, }, { - label: 'ZITADEL ACS Login Form', - url: `${environment.issuer}/ui/login/login/externalidp/saml/acs`, - }, - { - label: 'ZITADEL ACS Intent API', + label: 'ZITADEL ACS', url: `${idpBase}/acs`, }, { diff --git a/docs/docs/guides/integrate/identity-providers/azure-ad-saml.mdx b/docs/docs/guides/integrate/identity-providers/azure-ad-saml.mdx index 236488431..75d93bcfd 100644 --- a/docs/docs/guides/integrate/identity-providers/azure-ad-saml.mdx +++ b/docs/docs/guides/integrate/identity-providers/azure-ad-saml.mdx @@ -72,7 +72,7 @@ Now we configure the identity provider on ZITADEL. After you created the SAML provider in ZITADEL, you can copy the URLs you need to configure in your Entra ID application. -![Azure SAML App URLs](/img/guides/zitadel_azure_saml_provider_urls.png) +![Azure SAML App URLs](/img/guides/zitadel_saml_provider_urls.png) 1. Go to Microsoft Entra > Manage > Single sign-on 2. Edit the "Basic SAML Configuration" diff --git a/docs/docs/guides/integrate/identity-providers/linkedin_oauth.mdx b/docs/docs/guides/integrate/identity-providers/linkedin_oauth.mdx index 64d3153e6..4475735b2 100644 --- a/docs/docs/guides/integrate/identity-providers/linkedin_oauth.mdx +++ b/docs/docs/guides/integrate/identity-providers/linkedin_oauth.mdx @@ -1,6 +1,6 @@ --- title: Configure LinkedIn as an OAuth Identity Provider in ZITADEL -sidebar_label: LinkedIn generic OIDC +sidebar_label: LinkedIn generic OAuth id: linkedin-oauth --- @@ -23,8 +23,8 @@ import TestSetup from './_test_setup.mdx'; 2. Add your App Name, your Company Page and a Logo 3. Add "Sign In with LinkedIn using OpenID Connect" by clicking "Request access" 4. Go to the Auth Settings of the App and add the following URL to the "Authorized redirect URLs" - - `{your_domain}/ui/login/login/externalidp/callback` - - Example redirect url for the domain `https://acme.zitadel.cloud` would look like this: `https://acme.zitadel.cloud/ui/login/login/externalidp/callback` + - `{your_domain}/idps/callback` + - Example redirect url for the domain `https://acme.zitadel.cloud` would look like this: `https://acme.zitadel.cloud/idps/callback` 5. Verify the app as your company 6. In the Auth - OAuth 2.0 scopes section you should see `openid`, `profile` and `email` listed 7. Save Client ID and Primary Client Secret from the Application credentials diff --git a/docs/docs/guides/integrate/identity-providers/okta_saml.mdx b/docs/docs/guides/integrate/identity-providers/okta_saml.mdx index 49eb8e6c4..88d9976bd 100644 --- a/docs/docs/guides/integrate/identity-providers/okta_saml.mdx +++ b/docs/docs/guides/integrate/identity-providers/okta_saml.mdx @@ -35,7 +35,7 @@ As an alternative you can add the SAML identity provider through the API, either After you created the SAML Provider in ZITADEL, you can copy the URLs you need to configure in your OKTA application. -![OKTA SAML App URLs](/img/guides/zitadel_okta_saml_provider_urls.png) +![OKTA SAML App URLs](/img/guides/zitadel_saml_provider_urls.png) ## OKTA Configuration diff --git a/docs/static/img/guides/azure_app_redirects.png b/docs/static/img/guides/azure_app_redirects.png deleted file mode 100644 index 4368b4a2f..000000000 Binary files a/docs/static/img/guides/azure_app_redirects.png and /dev/null differ diff --git a/docs/static/img/guides/azure_app_register.png b/docs/static/img/guides/azure_app_register.png deleted file mode 100644 index f553c76b6..000000000 Binary files a/docs/static/img/guides/azure_app_register.png and /dev/null differ diff --git a/docs/static/img/guides/azure_app_registration.png b/docs/static/img/guides/azure_app_registration.png index 578666492..641fb918e 100644 Binary files a/docs/static/img/guides/azure_app_registration.png and b/docs/static/img/guides/azure_app_registration.png differ diff --git a/docs/static/img/guides/github_oauth_app_registration.png b/docs/static/img/guides/github_oauth_app_registration.png index 4350ea5e2..4ffd7528f 100644 Binary files a/docs/static/img/guides/github_oauth_app_registration.png and b/docs/static/img/guides/github_oauth_app_registration.png differ diff --git a/docs/static/img/guides/gitlab_app_id_secret.png b/docs/static/img/guides/gitlab_app_id_secret.png index c5495e102..4d1e818a8 100644 Binary files a/docs/static/img/guides/gitlab_app_id_secret.png and b/docs/static/img/guides/gitlab_app_id_secret.png differ diff --git a/docs/static/img/guides/gitlab_app_registration.png b/docs/static/img/guides/gitlab_app_registration.png index 3cec036e7..36e0c90f7 100644 Binary files a/docs/static/img/guides/gitlab_app_registration.png and b/docs/static/img/guides/gitlab_app_registration.png differ diff --git a/docs/static/img/guides/google_oauth_app_registration.png b/docs/static/img/guides/google_oauth_app_registration.png index 1008388ba..7493379a4 100644 Binary files a/docs/static/img/guides/google_oauth_app_registration.png and b/docs/static/img/guides/google_oauth_app_registration.png differ diff --git a/docs/static/img/guides/zitadel_apple_create_provider.png b/docs/static/img/guides/zitadel_apple_create_provider.png index 0b030ce71..a4836ef02 100644 Binary files a/docs/static/img/guides/zitadel_apple_create_provider.png and b/docs/static/img/guides/zitadel_apple_create_provider.png differ diff --git a/docs/static/img/guides/zitadel_azure_provider2.png b/docs/static/img/guides/zitadel_azure_provider2.png index 7651f1a18..1e9069882 100644 Binary files a/docs/static/img/guides/zitadel_azure_provider2.png and b/docs/static/img/guides/zitadel_azure_provider2.png differ diff --git a/docs/static/img/guides/zitadel_azure_saml_provider_urls.png b/docs/static/img/guides/zitadel_azure_saml_provider_urls.png deleted file mode 100644 index 585a96beb..000000000 Binary files a/docs/static/img/guides/zitadel_azure_saml_provider_urls.png and /dev/null differ diff --git a/docs/static/img/guides/zitadel_generic_oidc_create_provider.png b/docs/static/img/guides/zitadel_generic_oidc_create_provider.png index 4ca8b9a55..2e2abd58e 100644 Binary files a/docs/static/img/guides/zitadel_generic_oidc_create_provider.png and b/docs/static/img/guides/zitadel_generic_oidc_create_provider.png differ diff --git a/docs/static/img/guides/zitadel_github_create_provider.png b/docs/static/img/guides/zitadel_github_create_provider.png index 2687affc3..c551c0de2 100644 Binary files a/docs/static/img/guides/zitadel_github_create_provider.png and b/docs/static/img/guides/zitadel_github_create_provider.png differ diff --git a/docs/static/img/guides/zitadel_gitlab_create_provider.png b/docs/static/img/guides/zitadel_gitlab_create_provider.png index 2d1571946..b79ec247a 100644 Binary files a/docs/static/img/guides/zitadel_gitlab_create_provider.png and b/docs/static/img/guides/zitadel_gitlab_create_provider.png differ diff --git a/docs/static/img/guides/zitadel_google_create_provider.png b/docs/static/img/guides/zitadel_google_create_provider.png index ab2ebbb6b..3bd005d03 100644 Binary files a/docs/static/img/guides/zitadel_google_create_provider.png and b/docs/static/img/guides/zitadel_google_create_provider.png differ diff --git a/docs/static/img/guides/zitadel_okta_saml_provider_urls.png b/docs/static/img/guides/zitadel_okta_saml_provider_urls.png deleted file mode 100644 index a2c1e736d..000000000 Binary files a/docs/static/img/guides/zitadel_okta_saml_provider_urls.png and /dev/null differ diff --git a/docs/static/img/guides/zitadel_saml_provider_urls.png b/docs/static/img/guides/zitadel_saml_provider_urls.png new file mode 100644 index 000000000..7a955958f Binary files /dev/null and b/docs/static/img/guides/zitadel_saml_provider_urls.png differ diff --git a/internal/api/idp/idp.go b/internal/api/idp/idp.go index 0209e0337..acf835d02 100644 --- a/internal/api/idp/idp.go +++ b/internal/api/idp/idp.go @@ -9,13 +9,11 @@ import ( "io" "net/http" - "github.com/crewjam/saml" "github.com/gorilla/mux" "github.com/zitadel/logging" "github.com/zitadel/zitadel/internal/api/authz" http_utils "github.com/zitadel/zitadel/internal/api/http" - "github.com/zitadel/zitadel/internal/api/ui/login" "github.com/zitadel/zitadel/internal/command" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/form" @@ -53,13 +51,13 @@ const ( ) type Handler struct { - commands *command.Commands - queries *query.Queries - parser *form.Parser - encryptionAlgorithm crypto.EncryptionAlgorithm - callbackURL func(ctx context.Context) string - samlRootURL func(ctx context.Context, idpID string) string - loginSAMLRootURL func(ctx context.Context) string + commands *command.Commands + queries *query.Queries + parser *form.Parser + encryptionAlgorithm crypto.EncryptionAlgorithm + callbackURL func(ctx context.Context) string + samlRootURL func(ctx context.Context, idpID string) string + loginUICallbackRedirect func(w http.ResponseWriter, r *http.Request, state string) bool } type externalIDPCallbackData struct { @@ -91,27 +89,22 @@ func SAMLRootURL(externalSecure bool) func(ctx context.Context, idpID string) st } } -func LoginSAMLRootURL(externalSecure bool) func(ctx context.Context) string { - return func(ctx context.Context) string { - return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), externalSecure) + login.HandlerPrefix + login.EndpointSAMLACS - } -} - func NewHandler( commands *command.Commands, queries *query.Queries, encryptionAlgorithm crypto.EncryptionAlgorithm, externalSecure bool, instanceInterceptor func(next http.Handler) http.Handler, + loginUICallbackRedirect func(w http.ResponseWriter, r *http.Request, state string) bool, ) http.Handler { h := &Handler{ - commands: commands, - queries: queries, - parser: form.NewParser(), - encryptionAlgorithm: encryptionAlgorithm, - callbackURL: CallbackURL(externalSecure), - samlRootURL: SAMLRootURL(externalSecure), - loginSAMLRootURL: LoginSAMLRootURL(externalSecure), + commands: commands, + queries: queries, + parser: form.NewParser(), + encryptionAlgorithm: encryptionAlgorithm, + callbackURL: CallbackURL(externalSecure), + loginUICallbackRedirect: loginUICallbackRedirect, + samlRootURL: SAMLRootURL(externalSecure), } router := mux.NewRouter() @@ -189,22 +182,6 @@ func (h *Handler) handleMetadata(w http.ResponseWriter, r *http.Request) { } metadata := sp.ServiceProvider.Metadata() - for i, spDesc := range metadata.SPSSODescriptors { - spDesc.AssertionConsumerServices = append( - spDesc.AssertionConsumerServices, - saml.IndexedEndpoint{ - Binding: saml.HTTPPostBinding, - Location: h.loginSAMLRootURL(ctx), - Index: len(spDesc.AssertionConsumerServices) + 1, - }, saml.IndexedEndpoint{ - Binding: saml.HTTPArtifactBinding, - Location: h.loginSAMLRootURL(ctx), - Index: len(spDesc.AssertionConsumerServices) + 2, - }, - ) - metadata.SPSSODescriptors[i] = spDesc - } - buf, _ := xml.MarshalIndent(metadata, "", " ") w.Header().Set("Content-Type", "application/samlmetadata+xml") _, err = w.Write(buf) @@ -218,6 +195,9 @@ func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) { ctx := r.Context() data := parseSAMLRequest(r) + if h.loginUICallbackRedirect(w, r, data.RelayState) { + return + } provider, err := h.getProvider(ctx, data.IDPID) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -272,6 +252,9 @@ func (h *Handler) handleCallback(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } + if h.loginUICallbackRedirect(w, r, data.State) { + return + } intent, err := h.commands.GetActiveIntent(ctx, data.State) if err != nil { if zerrors.IsNotFound(err) { diff --git a/internal/api/ui/login/external_provider_handler.go b/internal/api/ui/login/external_provider_handler.go index ab0d98b99..fc0deff91 100644 --- a/internal/api/ui/login/external_provider_handler.go +++ b/internal/api/ui/login/external_provider_handler.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/crewjam/saml/samlsp" + "github.com/muhlemmer/gu" "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/client/rp" "github.com/zitadel/oidc/v3/pkg/oidc" @@ -13,8 +14,8 @@ import ( "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" - http_utils "github.com/zitadel/zitadel/internal/api/http" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" + idp_api "github.com/zitadel/zitadel/internal/api/idp" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore/v1/models" @@ -37,6 +38,8 @@ import ( const ( queryIDPConfigID = "idpConfigID" tmplExternalNotFoundOption = "externalnotfoundoption" + + prefixLoginState = "LOGIN_" ) type externalIDPData struct { @@ -98,6 +101,36 @@ type externalRegisterFormData struct { TermsConfirm bool `schema:"terms-confirm"` } +// IDPCallbackRedirect checks if the state parameter was set by the login UI +// and redirects the request to the login UI idp callback handlers (GET and POST). +// The function is used in the /idps/callback endpoint to distinguish between requests +// starte in the login UI and IdP intents. +func IDPCallbackRedirect(w http.ResponseWriter, r *http.Request, state string) bool { + state, loginUI := authRequestIDFromState(state) + if !loginUI { + return false + } + callback := EndpointExternalLoginCallback + if r.Method == http.MethodPost { + callback = EndpointExternalLoginCallbackFormPost + } + target := gu.PtrCopy(r.URL) + target.Path = HandlerPrefix + callback + q := target.Query() + q.Set("state", state) + target.RawQuery = q.Encode() + http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect) + return true +} + +func stateFromAuthRequest(authRequestID string) string { + return prefixLoginState + authRequestID +} + +func authRequestIDFromState(state string) (string, bool) { + return strings.CutPrefix(state, prefixLoginState) +} + // handleExternalLoginStep is called as nextStep func (l *Login) handleExternalLoginStep(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, selectedIDPID string) { for _, idp := range authReq.AllowedExternalIDPs { @@ -187,7 +220,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai return } params := l.sessionParamsFromAuthRequest(r.Context(), authReq, identityProvider.ID) - session, err := provider.BeginAuth(r.Context(), authReq.ID, params...) + state := stateFromAuthRequest(authReq.ID) + session, err := provider.BeginAuth(r.Context(), state, params...) if err != nil { l.renderLogin(w, r, authReq, err) return @@ -659,7 +693,7 @@ func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http data := new(externalNotFoundOptionFormData) authReq, err := l.ensureAuthRequestAndParseData(r, data) if err != nil { - l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err) + l.renderError(w, r, authReq, err) return } @@ -961,7 +995,7 @@ func (l *Login) googleProvider(ctx context.Context, identityProvider *query.IDPT return google.New( identityProvider.GoogleIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.GoogleIDPTemplate.Scopes, ) } @@ -980,7 +1014,7 @@ func (l *Login) oidcProvider(ctx context.Context, identityProvider *query.IDPTem identityProvider.OIDCIDPTemplate.Issuer, identityProvider.OIDCIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.OIDCIDPTemplate.Scopes, openid.DefaultMapper, opts..., @@ -1010,7 +1044,7 @@ func (l *Login) oauthProvider(ctx context.Context, identityProvider *query.IDPTe AuthURL: identityProvider.OAuthIDPTemplate.AuthorizationEndpoint, TokenURL: identityProvider.OAuthIDPTemplate.TokenEndpoint, }, - RedirectURL: l.baseURL(ctx) + EndpointExternalLoginCallback, + RedirectURL: idp_api.CallbackURL(l.externalSecure)(ctx), Scopes: identityProvider.OAuthIDPTemplate.Scopes, } return oauth.New( @@ -1028,6 +1062,7 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem if err != nil { return nil, err } + rootURL := idp_api.SAMLRootURL(l.externalSecure)(ctx, identityProvider.ID) opts := make([]saml.ProviderOpts, 0, 6) if identityProvider.SAMLIDPTemplate.WithSignedRequest { opts = append(opts, saml.WithSignedRequest()) @@ -1042,11 +1077,12 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem opts = append(opts, saml.WithTransientMappingAttributeName(identityProvider.SAMLIDPTemplate.TransientMappingAttributeName)) } opts = append(opts, - saml.WithEntityID(http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure)+"/idps/"+identityProvider.ID+"/saml/metadata"), + saml.WithEntityID(rootURL+"saml/metadata"), saml.WithCustomRequestTracker( requesttracker.New( - func(ctx context.Context, authRequestID, samlRequestID string) error { + func(ctx context.Context, state, samlRequestID string) error { useragent, _ := http_mw.UserAgentIDFromCtx(ctx) + authRequestID, _ := authRequestIDFromState(state) return l.authRepo.SaveSAMLRequestID(ctx, authRequestID, samlRequestID, useragent) }, func(ctx context.Context, authRequestID string) (*samlsp.TrackedRequest, error) { @@ -1064,7 +1100,7 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem )) return saml.New( identityProvider.Name, - l.baseURL(ctx)+EndpointExternalLogin+"/", + rootURL, identityProvider.SAMLIDPTemplate.Metadata, identityProvider.SAMLIDPTemplate.Certificate, key, @@ -1088,7 +1124,7 @@ func (l *Login) azureProvider(ctx context.Context, identityProvider *query.IDPTe identityProvider.Name, identityProvider.AzureADIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.AzureADIDPTemplate.Scopes, opts..., ) @@ -1102,7 +1138,7 @@ func (l *Login) githubProvider(ctx context.Context, identityProvider *query.IDPT return github.New( identityProvider.GitHubIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.GitHubIDPTemplate.Scopes, ) } @@ -1116,7 +1152,7 @@ func (l *Login) githubEnterpriseProvider(ctx context.Context, identityProvider * identityProvider.Name, identityProvider.GitHubIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.GitHubEnterpriseIDPTemplate.AuthorizationEndpoint, identityProvider.GitHubEnterpriseIDPTemplate.TokenEndpoint, identityProvider.GitHubEnterpriseIDPTemplate.UserEndpoint, @@ -1132,7 +1168,7 @@ func (l *Login) gitlabProvider(ctx context.Context, identityProvider *query.IDPT return gitlab.New( identityProvider.GitLabIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.GitLabIDPTemplate.Scopes, ) } @@ -1147,7 +1183,7 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider * identityProvider.GitLabSelfHostedIDPTemplate.Issuer, identityProvider.GitLabSelfHostedIDPTemplate.ClientID, secret, - l.baseURL(ctx)+EndpointExternalLoginCallback, + idp_api.CallbackURL(l.externalSecure)(ctx), identityProvider.GitLabSelfHostedIDPTemplate.Scopes, ) } @@ -1161,7 +1197,7 @@ func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTe identityProvider.AppleIDPTemplate.ClientID, identityProvider.AppleIDPTemplate.TeamID, identityProvider.AppleIDPTemplate.KeyID, - l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost, + idp_api.CallbackURL(l.externalSecure)(ctx), privateKey, identityProvider.AppleIDPTemplate.Scopes, )