diff --git a/cmd/start/start.go b/cmd/start/start.go index e424a3d903..c89f4361ee 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 245571e699..8b0ed04061 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 b0741c7b30..eede73dc8a 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 236488431f..75d93bcfdb 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 64d3153e6f..4475735b21 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 49eb8e6c44..88d9976bd0 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 4368b4a2ff..0000000000 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 f553c76b68..0000000000 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 578666492b..641fb918ef 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 4350ea5e2a..4ffd7528f8 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 c5495e1027..4d1e818a85 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 3cec036e71..36e0c90f76 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 1008388ba9..7493379a49 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 0b030ce718..a4836ef02b 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 7651f1a186..1e9069882e 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 585a96beb5..0000000000 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 4ca8b9a55a..2e2abd58e5 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 2687affc3c..c551c0de25 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 2d15719463..b79ec247a3 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 ab2ebbb6b0..3bd005d03a 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 a2c1e736d8..0000000000 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 0000000000..7a955958ff 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 0209e0337d..acf835d02f 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 ab0d98b997..fc0deff91e 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, )