revert: "feat(IDP): use single callback endpoint"

This reverts commit e126ccc9aa.

# Which Problems Are Solved

#8295 introduced the possibility to handle idps on a single callback,
but broke current setups.

# How the Problems Are Solved

- Revert the change until a proper solution is found. Revert is needed
as docs were also changed.

# Additional Changes

None.

# Additional Context

- relates to #8295
This commit is contained in:
Livio Spring 2024-07-24 14:29:05 +02:00 committed by GitHub
parent c3f8439a49
commit 8d13247413
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 65 additions and 80 deletions

View File

@ -425,6 +425,8 @@ 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
@ -488,8 +490,6 @@ 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

View File

@ -85,7 +85,7 @@ export class ProviderNextService {
map((env) => [
{
label: 'ZITADEL Callback URL',
url: `${env.issuer}/idps/callback`,
url: `${env.issuer}/ui/login/login/externalidp/callback`,
},
]),
);

View File

@ -84,7 +84,11 @@ export class ProviderSamlSpComponent {
downloadable: true,
},
{
label: 'ZITADEL ACS',
label: 'ZITADEL ACS Login Form',
url: `${environment.issuer}/ui/login/login/externalidp/saml/acs`,
},
{
label: 'ZITADEL ACS Intent API',
url: `${idpBase}/acs`,
},
{

View File

@ -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_saml_provider_urls.png)
![Azure SAML App URLs](/img/guides/zitadel_azure_saml_provider_urls.png)
1. Go to Microsoft Entra > Manage > Single sign-on
2. Edit the "Basic SAML Configuration"

View File

@ -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}/idps/callback`
- Example redirect url for the domain `https://acme.zitadel.cloud` would look like this: `https://acme.zitadel.cloud/idps/callback`
- `{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`
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

View File

@ -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_saml_provider_urls.png)
![OKTA SAML App URLs](/img/guides/zitadel_okta_saml_provider_urls.png)
## OKTA Configuration

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

@ -9,11 +9,13 @@ 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"
@ -51,13 +53,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
loginUICallbackRedirect func(w http.ResponseWriter, r *http.Request, state string) bool
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
}
type externalIDPCallbackData struct {
@ -89,22 +91,27 @@ 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),
loginUICallbackRedirect: loginUICallbackRedirect,
samlRootURL: SAMLRootURL(externalSecure),
commands: commands,
queries: queries,
parser: form.NewParser(),
encryptionAlgorithm: encryptionAlgorithm,
callbackURL: CallbackURL(externalSecure),
samlRootURL: SAMLRootURL(externalSecure),
loginSAMLRootURL: LoginSAMLRootURL(externalSecure),
}
router := mux.NewRouter()
@ -182,6 +189,22 @@ 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)
@ -195,9 +218,6 @@ 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)
@ -252,9 +272,6 @@ 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) {

View File

@ -6,7 +6,6 @@ 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"
@ -14,8 +13,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"
@ -38,8 +37,6 @@ import (
const (
queryIDPConfigID = "idpConfigID"
tmplExternalNotFoundOption = "externalnotfoundoption"
prefixLoginState = "LOGIN_"
)
type externalIDPData struct {
@ -101,36 +98,6 @@ 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 {
@ -220,8 +187,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
return
}
params := l.sessionParamsFromAuthRequest(r.Context(), authReq, identityProvider.ID)
state := stateFromAuthRequest(authReq.ID)
session, err := provider.BeginAuth(r.Context(), state, params...)
session, err := provider.BeginAuth(r.Context(), authReq.ID, params...)
if err != nil {
l.renderLogin(w, r, authReq, err)
return
@ -693,7 +659,7 @@ func (l *Login) handleExternalNotFoundOptionCheck(w http.ResponseWriter, r *http
data := new(externalNotFoundOptionFormData)
authReq, err := l.ensureAuthRequestAndParseData(r, data)
if err != nil {
l.renderError(w, r, authReq, err)
l.renderExternalNotFoundOption(w, r, authReq, nil, nil, nil, err)
return
}
@ -995,7 +961,7 @@ func (l *Login) googleProvider(ctx context.Context, identityProvider *query.IDPT
return google.New(
identityProvider.GoogleIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.GoogleIDPTemplate.Scopes,
)
}
@ -1014,7 +980,7 @@ func (l *Login) oidcProvider(ctx context.Context, identityProvider *query.IDPTem
identityProvider.OIDCIDPTemplate.Issuer,
identityProvider.OIDCIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.OIDCIDPTemplate.Scopes,
openid.DefaultMapper,
opts...,
@ -1044,7 +1010,7 @@ func (l *Login) oauthProvider(ctx context.Context, identityProvider *query.IDPTe
AuthURL: identityProvider.OAuthIDPTemplate.AuthorizationEndpoint,
TokenURL: identityProvider.OAuthIDPTemplate.TokenEndpoint,
},
RedirectURL: idp_api.CallbackURL(l.externalSecure)(ctx),
RedirectURL: l.baseURL(ctx) + EndpointExternalLoginCallback,
Scopes: identityProvider.OAuthIDPTemplate.Scopes,
}
return oauth.New(
@ -1062,7 +1028,6 @@ 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())
@ -1077,12 +1042,11 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem
opts = append(opts, saml.WithTransientMappingAttributeName(identityProvider.SAMLIDPTemplate.TransientMappingAttributeName))
}
opts = append(opts,
saml.WithEntityID(rootURL+"saml/metadata"),
saml.WithEntityID(http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure)+"/idps/"+identityProvider.ID+"/saml/metadata"),
saml.WithCustomRequestTracker(
requesttracker.New(
func(ctx context.Context, state, samlRequestID string) error {
func(ctx context.Context, authRequestID, 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) {
@ -1100,7 +1064,7 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem
))
return saml.New(
identityProvider.Name,
rootURL,
l.baseURL(ctx)+EndpointExternalLogin+"/",
identityProvider.SAMLIDPTemplate.Metadata,
identityProvider.SAMLIDPTemplate.Certificate,
key,
@ -1124,7 +1088,7 @@ func (l *Login) azureProvider(ctx context.Context, identityProvider *query.IDPTe
identityProvider.Name,
identityProvider.AzureADIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.AzureADIDPTemplate.Scopes,
opts...,
)
@ -1138,7 +1102,7 @@ func (l *Login) githubProvider(ctx context.Context, identityProvider *query.IDPT
return github.New(
identityProvider.GitHubIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.GitHubIDPTemplate.Scopes,
)
}
@ -1152,7 +1116,7 @@ func (l *Login) githubEnterpriseProvider(ctx context.Context, identityProvider *
identityProvider.Name,
identityProvider.GitHubIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.GitHubEnterpriseIDPTemplate.AuthorizationEndpoint,
identityProvider.GitHubEnterpriseIDPTemplate.TokenEndpoint,
identityProvider.GitHubEnterpriseIDPTemplate.UserEndpoint,
@ -1168,7 +1132,7 @@ func (l *Login) gitlabProvider(ctx context.Context, identityProvider *query.IDPT
return gitlab.New(
identityProvider.GitLabIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.GitLabIDPTemplate.Scopes,
)
}
@ -1183,7 +1147,7 @@ func (l *Login) gitlabSelfHostedProvider(ctx context.Context, identityProvider *
identityProvider.GitLabSelfHostedIDPTemplate.Issuer,
identityProvider.GitLabSelfHostedIDPTemplate.ClientID,
secret,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.GitLabSelfHostedIDPTemplate.Scopes,
)
}
@ -1197,7 +1161,7 @@ func (l *Login) appleProvider(ctx context.Context, identityProvider *query.IDPTe
identityProvider.AppleIDPTemplate.ClientID,
identityProvider.AppleIDPTemplate.TeamID,
identityProvider.AppleIDPTemplate.KeyID,
idp_api.CallbackURL(l.externalSecure)(ctx),
l.baseURL(ctx)+EndpointExternalLoginCallbackFormPost,
privateKey,
identityProvider.AppleIDPTemplate.Scopes,
)