Integrate RP with tunneler and introspect request

Hi,
I got a internal website - RP1(http server) which originally required authentication by another internal OIDC SSO.
Now I would like to add this RP1 to my new Ziti network by openziti tunneler, and configures external-jwt-signer with Auth0 OIDC.

After I use browZer's feature to login and get connected to RP1,
I will need my original OIDC SSO authentication again.

In this scenario, is there a formal way to let RP1 know this request comes from an authenticated client? For example, should I pass cookie "__ziti-browzer-config" to some endpoint for validation and get user's information?

Thanks!

1 Like

A lot of IdP allow for federating identity. Not all IdPs allow it and it might not make sense in your/all cases. Browzer by it's nature, will require you to have a publicly available IdP. If the private IdP were configured to trust the public one, then I would think it would work. You can setup keycloak like that (among others). For example, my browzer setup https://brozac.clint.demo.openziti.org/ - you need to auth with github or google but it's THROUGH keycloak. If the private IdP was configured to trust the public one used for Browzer, I would expect your "single sign-on" to work implicitly.

Are you a developer of the RP1 app? It sounds like you're able to make a change to it? the __ziti-browzer-config cookie won't be much use to you. I don't see any Authentication cookies passed through to the target application. I'd think the only way you'd get it to work is to allow the private IdP to delegate to the public one.

Maybe @curt or @andrew.martinez or someone else in the community is aware of something I'm not though.

1 Like

Hi @TheLumberjack ,
I'm very appreciated for your instant response :slight_smile: !
Yes, I'm the owner of the RP1 app, actually RP1 is just my tiny server side render demo app for testing.

I made an experiment to do this after I submit this post, and I think it was very similar to what you said:

  1. configure ext-jwt-signer with auth0 which will federated to github.com for authentication, and do what ever I need to do to make it work with BrowZer according to the docs.
  2. configure my RP1 app with auth0 which will also federated to github.com, and set it to another "regular web app" oauth2 client, it will use the code grant process.

So I expect after the 1st step, the github authentication session will exist, the 2nd step won't need to authenticate the user again. And this worked as I expected.

Unfortunately, I found that the 2nd step's oauth2 query parameter(code & state) will cause infinite browser redirection loop. I guess the state parameter is the major one confuses openziti, because it might think this was belongs to some ext-jwt-signer authentication process and should already be saved in memory/db, but it wasn't, it belongs to the upstream's RP1.
So my testing was sadly failed.

I also found that these interesting test case:

  1. /?code=1&state=1 will fail(infinite redireciton)
  2. /?code=1&auth_state=1 will fail(infinite redireciton)
  3. /?code=1&sstate=1 will fail(infinite redireciton)
  4. /?code=1&sstaate=1 will pass
  5. /?code=1 will pass

So I guess somewhere in the source code is running some string search logic for 'state' in the URL...

My deployment versions
ZITI_VERSION: 0.31.4
ziti-browzer-bootstrapper:0.49.1

I highly suspect this line(check below) is the root cause for the problem I met:

file name in broser: sziti-browzer-sw.js?swVersion=0.46.1&controllerApi=…
openziti/ziti-browzer-sw

  const matchGETCb = (url, request) => {
      let getURL = new URL(url);
      if (getURL.pathname.includes("browzer_error")) {
          return false;
      }
      if (typeof self._zitiConfig === 'undefined') {
          return true;
      }
      //  I suspect this line causes my loop
      if (getURL.search.includes("code=") && getURL.search.includes("state=")) {
          return false;
      }
      let controllerURL = new URL(self._zitiConfig.controller.api);
      if (url.hostname === controllerURL.hostname) {
          return false;
      }
      else {
          return true;
      }
  };

I will try to fix it ...

1 Like

@rickwang7712 the scenario you have encountered is one I refer to as "nested-SSO". This capability is not yet supported in browZer, but is certainly on my backlog, and is near the top of the list (we need it internally here at NetFoundry, fwiw).

2 Likes

@curt Thank you for your response!

I was thinking the solution for this nested-sso scenario as below:
Option 1. The framework needs a "sso callback whitelist path configuration" for bypassing the callback parameters and requests to the internal RPs.
Option 2. Only consumes the sso callback parameters if the ziti session was invalid.

Considering sometimes we might need re-authenticate user, option 1 might be a feasible way.
Is there a finalized implementation plan/spec for this?
Thanks!

Update:

I implemented Option 1 and it works fine!
However, I found another interesting situation:
My RP's cookie will not appear in my browser, but my backend can still receive it after every request triggered from browser.

Is this an expected behavior?
Thanks!

1 Like

Is there a PR (or set of them)?

Not sure I follow. Can you elaborate on why you think a cookie that is NOT in the browser IS being transmitted to your web server?

@curt My pull request is here: fix: inaccurate oauth2 callback parameters matching in matchGetCb by rickwang7712 · Pull Request #201 · openziti/ziti-browzer-sw · GitHub
Another part to support Nested-SSO will be submitted to another pull request later.

I wrote a Golang http server using Gin as my RP's backend, this backend replies server side rendered index.html to the browser. For debugging, I print all the http headers to the console received by the server for each http request.

After I finish the login process(Nested SSO), my backend server replies a Set-Cookie Header(Set-Cookie: rp1_id=raw_idtoken) back to the frontend. However, while I opened Chrome's Dev-Tool and check the "Application" tab, I only see openziti-created cookies are listed.
But if I refresh the RP's web page, my backend server can still receive the rp1_id cookie. So I'm pretty sure my cookie was stored somewhere, it's just I cannot see it via browser's dev tool.
Moreover, the header Set-Cookie: rp1_id=raw_idtoken didn't appears to the corresponding request's response; when I reload the page, the header "Cookie: rp1_id=raw_idtoken" didn't appear, either. But my backend still received it.

So I'm really confused how this works, is there a proxy component(js runtime ?) helping me memorizing my cookies?

Another symptom that I found is the cookie flags of my cookie rp1_id will be sent together to my backend: ...; HttpOnly=undefined; Secure=undefined;
So I guess the cookie jar is implemented on somewhere written in Javascript?