Tetsujin no Oni
Tetsujin no Oni

Reputation: 7367

Google Cloud Identity Platform (CICP) SAML Workflow Fails

Background

Using Firebase Auth and a SAML Auth Provider with the following configuration:

const config = {
    apiKey: "AIzaSy...",
    authDomain: "example-app.firebaseapp.com",
};

firebase.initializeApp(config);

const provider = new firebase.auth.SAMLAuthProvider('saml.example-idp');

function saml() {
    firebase.auth().signInWithRedirect(provider)
        .then((result) => {
            console.log(result);
        })
        .catch((error) => {
            console.log(error);
        });
}

The CICP configuration for the SAML upstream has the Service Provider: our entity id and the ACS configured as our CICP https://example-app.firebaseapp.com/__/auth/handler.

What I expect to happen

To be able to set a breakpoint in the signInWithRedirect()'s Promise's then and see the authenticated user.

What actually happens

Flow is redirected to the IdP and authentication handled.

The IdP emits the redirect-posting page with automatic submit-on-load and a multipart/form-data form with:

This in turn causes CICP to render and return a page with script that sets up

var POST_BODY=""------WebKitFormBoundary9bn7AOpnZiIRk9qZ\r\nContent....."

i.e. rather than parsing the form body and extracting the SAMLResponse field, it is replaying the entire Request.body into the script and then calling fireauth.oauthhelper.widget.initialize();

This obviously fails because that roundtrips and then attempts to post the entire response body to the /__/auth/handler endpoint as a querystring.

I suspect there's a simple configuration item that's missing from this chain, because all of the flows look normal to me until the multipart form data gets pushed into the POST_BODY and then prevents the transform of the SAML token into an OAuth token.

The question

What configuration item is incorrect in this (redacted) setup, and what is the correct derivation of the value to replace it with?

Upvotes: 3

Views: 653

Answers (2)

Tetsujin no Oni
Tetsujin no Oni

Reputation: 7367

Short answer to long question:

The SAML provider handling in Firebase Auth and Google CICP doesn't process multipart/form-data and needs to be in application/x-www-form-urlencoded.

This is a SAML IdP configuration, not something which can be handled by the Firebase Auth service provider configuration.

Upvotes: 1

Thierry Falvo
Thierry Falvo

Reputation: 6290

Maybe there is also an additional technical issue with SAML, but at least there's a incoherence point in the way sign-in method is used.

According to (Official Docs)[https://cloud.google.com/identity-platform/docs/how-to-enable-application-for-saml#handle-signin-with-client-sdk], you have 2 options to sign-in :

1) With Popup

In this case, you can use a promise to retrieve user credential with sign-in method:

firebase.auth().signInWithPopup(provider)
    .then((result) => {
      // User is signed in.
      // Identity provider data available in result.additionalUserInfo.profile,
      // or from the user's ID token obtained via result.user.getIdToken()
      // as an object in the firebase.sign_in_attributes custom claim
      // This is also available via result.user.getIdTokenResult()
      // idTokenResult.claims.firebase.sign_in_attributes.
    })
    .catch((error) => {
      // Handle error.
    });

2) With Redirect

In this case, your code should be split into 2 parts. First sign-in method, without using any promise :

 firebase.auth().signInWithRedirect(provider);

Then, the initialization of a "listener", to retrieve user credential after the sign-in redirect :

// On return.
firebase.auth().getRedirectResult()
    .then((result) => {
      // User is signed in.
      // Identity provider data available in result.additionalUserInfo.profile,
      // or from the user's ID token obtained via result.user.getIdToken()
      // as an object in the firebase.sign_in_attributes custom claim
      // This is also available via result.user.getIdTokenResult()
      // idTokenResult.claims.firebase.sign_in_attributes.
    })
    .catch((error) => {
      // Handle error.
    });  

To be added in the bootstrap part of your page/app.

Hope it will help.

Upvotes: 1

Related Questions