ctron / yew-oauth2

General purpose OAuth2 component for Yew
Apache License 2.0
44 stars 17 forks source link

Using Auth0 as IDP and manage on Client the redirection. #23

Closed AlexandreRoba closed 8 months ago

AlexandreRoba commented 8 months ago

Hi @ctron , Thanks for replying to me.

Here is the issue I'm facing. I'm trying to use Auth0 as IDP and I need to configure on Auth0 the authorized returnUrls. Auth0 does not allow to have "Generic" returnURLs. So we need to have a fixed set of defined returned Url.

But when using Yew-Oauth (Which is a great Thanks for that :p) the token lives in the app context.So on any refresh or reload of the page we loose this token and we are redirected to auth0 to get a new token. Which makes full sens. But we can be redirected from any page on the app which requires authentication such as for example https://localhost:3000/orders/xxxx/items/details...... xxx could be infinite... So no way to configure this on Auth0.

One way to tackle this could be to use on our app a defined url such as /callback?appReturnUrl=<<https://localhost:3000/orders/xxxx/items/details must be url encoded of course :p>> or a state parameter which will contains the url of the protected ressource and redirect the user to this value when /callback is called.

In order to do so we need to be able to "Inject" the returnUrl when redirecting to the authorization endpoint when accessing a protected ressource taking the current page url and add it as the appREturnURl or state.

My understanding is that we should use for this the LoginOptions parameters, so as a first step I used this code:

let login_options = LoginOptions::new()
        .with_redirect_url(Url::parse("http://localhost:3000/callback").unwrap());
    html!(
        <>
            <OAuth2 {config}
                scopes={vec!["openid".into(),"email".into(),"offline_access".into(),"api:call".into()]}
                audience={"http://localhost:8081/api"}
                options={login_options}>
                <Content/>
            </OAuth2>
        </>
    )

My understanding is that it is supposed to override the default returnUrl with what it is specified on the with_redirect_url. But it does not. When the user tries to access a protected ressources he still get as returnUrl the url of the protected ressource:

response_type: code
client_id: XXXXXXXXXXXX
state: eMhCkjz4-qTShr93Iw-qmw
code_challenge: lwo9FNEKznQ7xgho_ylMrxU_71zHzQoHlq907uQ83yY
code_challenge_method: S256
redirect_uri: http://localhost:3000/
scope: openid openid email offline_access api:call
audience: http://localhost:8081/api
nonce: B4qwU8ekDmymf9b6Mzongw

Once I have this working I will need a way on this configuration to inject the url of the protected ressource as the appReturnURL...

I do not see how to do this differently in order to implement the silent login on an unlimited custom list of return urls with auth0 as they do not support wildcard parameters on the return url :(

This is also what is explain here https://auth0.com/docs/authenticate/login/redirect-users-after-login.

ctron commented 8 months ago

There's a LoginOptions struct which allows to supply the redirect URL: https://github.com/ctron/yew-oauth2/blob/c60380d42d321de817059370dba39705cdba88b5/src/components/context/mod.rs#L43

It defaults to the current URL. I am not sure this is exposed at the moment. But I think this should be the right feature to leverage.

ctron commented 8 months ago

Ah, double checking … it's exposed as part of the OAuth2 component.

ctron commented 8 months ago

Ah … and triple checking (should have done that first) … that was you already use. Hm …

ctron commented 8 months ago

Ok, checking the Auth0 docs, that looks like an interesting limitation. IIRC Keycloak does allow a prefix, and even allow for certain wildards.

So I guess you need to follow the idea of Auth0, and encode this somewhere in a cookie, session, or state variable.

AlexandreRoba commented 8 months ago

Hi @ctron,

Yes indeed. Unless I'm mistaken but I have set LoginOptions but it does not seems to be taken into account. :( It is always sets the returnUrl as the one of the ressource.

My second problem is to be able to capture the url of the ressource that I'm trying to access in order to be able to pass it in a state or a cookie to the authorization endpoint. Is this supported? An idea on how to do this?

ctron commented 8 months ago

Taking a closer look at the code, I think everything should already be there: https://github.com/ctron/yew-oauth2/blob/62187a9a3985843699cd1f850ce32f674945a9fe/src/agent/mod.rs#L441-L450

I am not sure why it doesn't work … I guess you will need to debug this.

AlexandreRoba commented 8 months ago

Hi @ctron. I'm trying to find out what is going on. I have forked the solution and set couple of log points: I can see the LoginOptions is used and set in the agent context using the OAuth component. But then once I start the login process the LoginOptions is back to the default value.

Screenshot 2024-01-11 at 13 48 55

There is something clearly happening somewhere that cleans the LoginOptions because the audience and the scopes are conserved which are set at the same place are conserved:

<OAuth2 {config}
                scopes={vec!["openid".into(),"email".into(),"offline_access".into(),"api:call".into()]}
                audience={"http://localhost:8081/api"}
                options={login_options}>
                <Content/>
</OAuth2>
ctron commented 8 months ago

Weird indeed. But I have no idea what's going on. And you seem to have a reproducer at hand :)

AlexandreRoba commented 8 months ago

I've found this article that describe the need for anyone else reading the issue https://community.auth0.com/t/how-do-i-set-up-a-dynamic-allowed-callback-url/60268

@ctron I'm going to investigate a little further but I'm not even sure I will be able to capture and store the protected ressource url with the yew-oauth2 API before starting the login process. I'm wondering if I would not be better building my own custom oauth agent for auth0. I need to have it working now. :( thanks for the help you provided.

AlexandreRoba commented 8 months ago

@ctron Do you know why is LoginOption passed as parameters on the start_login?

  fn start_login(&mut self, options: LoginOptions) -> Result<(), OAuth2Error> {
        let client = self.client.as_ref().ok_or(OAuth2Error::NotInitialized)?;
        let config = self.config.as_ref().ok_or(OAuth2Error::NotInitialized)?;
        log::info!("start_login config are: {:?}", self.config);
        let redirect_url = match options.redirect_url {
            Some(redirect_url) => redirect_url,
            None => Self::current_url().map_err(OAuth2Error::StartLogin)?,
        };

Cause LoginOptions is an attribute of the config and is set there. I mean InnerConfig has an attributes option which contains the proper LoginOption value....

ctron commented 8 months ago

IIRC the original ideas was to allow one to manually start a login process (e.g. from your own component) with some additional options.

ctron commented 8 months ago

@ctron I'm going to investigate a little further but I'm not even sure I will be able to capture and store the protected ressource url with the yew-oauth2 API before starting the login process.

In the code I linked earlier, you will find 3 variables which are stored in the session store. I would suggest to add that information there. Then check the corresponding section where those variables are read again, and I you find our value stored, apply it.

I'm wondering if I would not be better building my own custom oauth agent for auth0. I need to have it working now. :(

I you believe that to the faster, that might be your better approach then. But from what I see, it should just be a few changes. But I also can't do that for you, as I don't have your environment set up, and also don't have the time to invest into that issue right now.

AlexandreRoba commented 8 months ago

IIRC the original ideas was to allow one to manually start a login process (e.g. from your own component) with some additional options.

That is an excellent idea! I could leverage this. Then I guess this is where it happens. It never takes the one coming from the inner config and takes the one from the "manual" start and this override the one set on the OAuth2 component. I will look into this.

kate-shine commented 8 months ago

Thanks for dealing with this :) I'm facing the same issue with app using Microsoft Entra as IDP. If you need any testing or help, please let me know

ctron commented 8 months ago

Ok, I dug a bit into this, the reason for this is that the Redirect component calls start_login with default options. And when evaluating the agent doesn't take into consideration the "agent configured" login options.

Good news, this should be an easy fix.

ctron commented 8 months ago

@kate-shine @AlexandreRoba there's a PR for this now: https://github.com/ctron/yew-oauth2/pull/24 … it would be great if you could give it a try.

You should be able to do this using:

[patch.crates-io]
yew-oauth2 = { git = "https://github.com/ctron/yew-oauth2", rev = "4342e94907799da7d305492e0b7df3a8326373b4" } 
AlexandreRoba commented 8 months ago

@ctron thanks a lot for this. I was dragged into other issues. i will give it a try this we.