feat(IDP): use single callback endpoint (#8295)
# Which Problems Are Solved Both the login UI and the IdP intent flow have their own IdP callback endpoints. This makes configuration hard to impossible (e.g. Github only allows one endpoint) for customers. # How the Problems Are Solved - The login UI prefixes the `state` parameter when creating an auth / SAML request. - All requests now use the `/idp/callback` or the corresponding variation (e.g. SAML) - On callback, the state, resp. its prefix is checked. In case of the login UI prefix, the request will be forwarded to the existing login UI handler without the prefix state. Existing setups will therefore not be affected and also requests started before this release can be handled without any impact. - Console only lists the "new" endpoint(s). Any `/login/externalidp/callback` is removed. # Additional Changes - Cleaned up some images from the IdP documentation. - fix the error handling in `handleExternalNotFoundOptionCheck` # Additional Context - closes #8236
@ -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
|
||||
|
@ -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`,
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
@ -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`,
|
||||
},
|
||||
{
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
BIN
docs/static/img/guides/azure_app_redirects.png
vendored
Before Width: | Height: | Size: 127 KiB |
BIN
docs/static/img/guides/azure_app_register.png
vendored
Before Width: | Height: | Size: 85 KiB |
BIN
docs/static/img/guides/azure_app_registration.png
vendored
Before Width: | Height: | Size: 327 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 87 KiB |
BIN
docs/static/img/guides/gitlab_app_id_secret.png
vendored
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 87 KiB |
BIN
docs/static/img/guides/gitlab_app_registration.png
vendored
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 339 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 97 KiB |
BIN
docs/static/img/guides/zitadel_azure_provider2.png
vendored
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 75 KiB |
BIN
docs/static/img/guides/zitadel_saml_provider_urls.png
vendored
Normal file
After Width: | Height: | Size: 98 KiB |
@ -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) {
|
||||
|
@ -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,
|
||||
)
|
||||
|