Can Apigee act as a SAML identity provider?

Is it possible to set up an Apigee proxy that can act as a SAML IdP using the GenerateSAML assertion policy? 

We're currently using Firebase to manage users . However, Firebase cannot act as a SAML IdP and we'd like users to be able to log into the integrated developer portal with the same credentials. 

 

Solved Solved
1 11 267
1 ACCEPTED SOLUTION

The signin page can send a GET back to the IDP proxy by just setting window.location.href. In the working implementation I built, it looks like this: 

 

   window.location.href = `/apigee-saml-idp/authcomplete?id=${authResult.idToken}&session=${sess}`;

 

This way the browser passes the ID Token and the session identifier back to the proxy, via query parameters in a simple GET call.  

The proxy then uses that ID token and the session identifier to produce a SAML Response with an Assertion of identity.  This SAML Response needs to go to the Integrated developer portal. That needs to be done by the browser.  So the response from the IDP proxy  to the GET call from the browser that I mentioned above... is ... a webpage, which holds a FORM that posts the SAML Response back to the Integrated Developer portal signin endpoint. 

It looks like this: 

 

<html>
  <head>
    <title>Sign-In Complete</title>
    <link rel="stylesheet" href="signin/css/main.css"/>
  </head>

  <body>
    <div class='maincontainer'>
      <div class='flexboxcontainer'>
        <form role='form' method="post" action="{session.ACSURL}">
          <input type="hidden" name="SAMLResponse" value="{SAMLResponse}" />
          <div class='button-group'>
            <button class='when-no-signedin btn btn-sm btn-outline-primary' id='btn-signin' type='submit'>Complete Sign In</button>
          </div>
        </form>
      </div>
    </div>

    <script charset="utf-8">
     document.addEventListener("DOMContentLoaded", (_event) => {
       document.forms[0].submit();
     });
    </script>
  </body>

</html>

 

And when the developer portal receives this (at the ACSURL) , it accepts that SAML Assertion as confirmation that the user should be signed in.  

Done. 

It's easier to understand if you see the interactions. Here's a recording: https://youtu.be/O5oDMCbIT0I

 

View solution in original post

11 REPLIES 11

I found this repo, which seems to be configuring Apigee to act as a SAML identity provider but without any actual end-user credentials verification. Just to confirm, before the generateSAMLAssertion policy runs, we can add a ServiceCallout to our Firebase Auth service to allow the user to log in through Firebase verified credentials? 

 

Are there any restrictions or considerations with this approach? 

Yes, you can probably do what you describe.  To re-state it, I think you want to build a SAML IDP, using Firebase Auth (Cloud Identity) as the backend. 

This question has been asked before . I don;t know of anyone who has done this, and described it.  

If you search for "SAML" and "Firebase Auth" the results will show you how to connect an existing SAML IDP into the Firebase Auth federated identity model.  That's not what you're asking about. If I am understanding correctly, you want to build a new SAML IDP, using Firebase Auth as the identity source.

It seems possible, but will take some effort on your part. Here is a start. That is a simple SAML IDP that uses a mock service as a backend. You would need to modify that sample to 

  • use Firebase Auth as the backend.  AKA, verify user credentials with Firebase Auth (aka Cloud Identity);
  • work on Apigee X

For the first part, I think the IDP proxy might respond to a request to /login ,  via an HTTP redirect, pointing the user to a specific signin page that you provide. That would allow signin via "normal" Firebase Auth UX (or even SSO, if the user is already signed in). Then, the result of that would redirect back to your IDP proxy, which would create the SAML assertion and present it to the Developer Portal. 

 This project shows how to build an OIDC Identity Provider using Firebase Auth (Cloud Identity) as the backend.  You want to do something similar, but building a SAML IDP (not OIDC), using Firebase Auth (Cloud Identity) as the backend. 

 

 

Thanks for the details! 

Regarding "Then, the result of that would redirect back to your IDP proxy, which would create the SAML assertion and present it to the Developer Portal." Could you provide more details on what this could look like? Does the backend redirect back to the same IdP proxy endpoint (/login)? Would the backend need to provide any token indicating the user is authenticated? 

Yes, the idea is simple: Build an Apigee API Proxy that exposes the interface of a SAML Identity Provider (/login and /logout endpoints).  

The implementation is a little involved. I've implemented it, and it works. 

Here's the general idea:

The user clicks "login with SAML", and this causes the integrated developer portal to construct a SAML AuthnRequest, and send it to the /login endpoint.  This endpoint is handled by the Apigee API Proxy acting as the SAML IDP.  The proxy validates the SAML request, and then redirects the user to a page that allows the user to signin to Cloud Identity (aka Firebase Authn). 

Depending on browser state and cookies, this signin may allow the Single Sign-on experience, or it may require the user to explicitly signin with credentials. Either way, the result of a successful authentication is an ID token provided by Firebase Auth. Then, when that completes, the signin page sends the ID Token back to the API Proxy, which can verify the token, and use the contents of that token to construct a response to the AuthnRequest. That response is a SAML Assertion, stating the name, email, issue instant, etc for the signed-in user. 

The signin experience then sends that SAML response back to the Integrated Developer Portal, and the portal treats the user as signed in. 

I'll give more details in a bit. 

Thanks that makes sense at a high-level but I was wondering how the login-in page sends the ID token back to the API proxy since the user was redirected to the login page. Can the redirection to the login page happen within the original AuthnRequest handling before the AuthnResponse is sent back? 

The signin page can send a GET back to the IDP proxy by just setting window.location.href. In the working implementation I built, it looks like this: 

 

   window.location.href = `/apigee-saml-idp/authcomplete?id=${authResult.idToken}&session=${sess}`;

 

This way the browser passes the ID Token and the session identifier back to the proxy, via query parameters in a simple GET call.  

The proxy then uses that ID token and the session identifier to produce a SAML Response with an Assertion of identity.  This SAML Response needs to go to the Integrated developer portal. That needs to be done by the browser.  So the response from the IDP proxy  to the GET call from the browser that I mentioned above... is ... a webpage, which holds a FORM that posts the SAML Response back to the Integrated Developer portal signin endpoint. 

It looks like this: 

 

<html>
  <head>
    <title>Sign-In Complete</title>
    <link rel="stylesheet" href="signin/css/main.css"/>
  </head>

  <body>
    <div class='maincontainer'>
      <div class='flexboxcontainer'>
        <form role='form' method="post" action="{session.ACSURL}">
          <input type="hidden" name="SAMLResponse" value="{SAMLResponse}" />
          <div class='button-group'>
            <button class='when-no-signedin btn btn-sm btn-outline-primary' id='btn-signin' type='submit'>Complete Sign In</button>
          </div>
        </form>
      </div>
    </div>

    <script charset="utf-8">
     document.addEventListener("DOMContentLoaded", (_event) => {
       document.forms[0].submit();
     });
    </script>
  </body>

</html>

 

And when the developer portal receives this (at the ACSURL) , it accepts that SAML Assertion as confirmation that the user should be signed in.  

Done. 

It's easier to understand if you see the interactions. Here's a recording: https://youtu.be/O5oDMCbIT0I

 

Thank you!! The video recording was very helpful to understand the full flow. 

When the proxy sends a POST response back to the integrated portal, does the integrated portal create a new developer in Apigee associated with the Firebase authenticated user?

 

Yes, it does. 

Also - In Google Cloud Identity, there is a way to insert Google Cloud Functions as "blocking hooks" that execute before a person signs in.  Which means if you wanted to filter or check the person signing in, you could do that, with the beforeSignIn hook.  You might check to see that the person is on a pre-allocated allow list, for example. 

Oh, and here's the example API Proxy: https://github.com/DinoChiesa/Apigee-SAML-IDP-for-Firebase

 

Thanks for all your help! Is it possible to attach the user's Firebase UUID in the developer entity as well during this flow? 

Alternatively, Apigee could do a service call out with the developer's email address to Firebase in order to retrieve the UUID to pass it to backend APIs? 

ahh
With the SAML sign in, the ID token issued by Firebase obviously includes the unique user id. I think it's not a uuid but rather just a "subject id", and it is carried in the "sub" claim. And the SAML Assertion produced by the IdP I built, does include this claim as an Attribute in the AttributeStatement (standard part of a SAML Assertion response to AuthnRequest).

But, the developer portal doesn't DO anything with that Attribute; and specifically it doesn't attach that ID to the developer entity during auto-registration at sign-in. It would be nice if the developer portal did that automatically, and I think it's a good feature enhancement request, but today that does not happen.

You suggested that Apigee could "call out" to ...something... with the developer's email address, to retrieve that ID. And the answer to that is YES, you can do that. You would need to configure the service callout to make a call like this:

 

POST :idtk/v1/projects/:proj/accounts:lookup
x-goog-user-project: :proj
Authorization: Bearer :token
content-type: application/json

{
  "email" : [ "username@gmail.com" ]
}

 

Documented here. And the result there includes a localId, which is the same as the "sub" claim. You could then attach that to the developer entity, and subsequently skip that ServiceCallout.

One note. The x-goog-user-project header there is required if you are invoking the REST API directly. This obscure header is documented here. Here's why it's needed. Many (most) APIs or services in the googleapis.com universe are resource-billed APIs. If you use them, then the billing accrues to the resource itself. A good example is BigQuery; if you send queries, the project hosting the BigQuery is the one that gets billed. Similarly for storage, or VertexAI, or etc. The identitytookit api is different. It is what is known as a client-based service, per that documentation. I think that's misleading or confusing, or just wrongly worded. It's client-based billing. Or better phrased as client-billed which I think captures the essence of it better. It says the CALLER gets billed. Not the project that owns the resource. So when you do a lookup on a user, YOU the caller get billed, not the project that "manages" the identity platform configuration. I amnot sure why it's important to use different billing models, but I guess it makes sense for an IdP. that way if some project issues lots of requests against the identity platform, the caller pays the cost. ok makes sense. ANYWAY, you must pass the x-goog-user-project header to indicate which project to bill ! That project might be distinct from the project that is the home of the identity platform.

Obviously you need the appropriate authorization in order to perform that call. Per the doc, Authorization requires the following IAM permission on the specified target Project: firebaseauth.users.get .