dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.

Home Page:https://asp.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Microsoft.AspNetCore.Authentication.OpenIdConnect] Manage Oidc workflow in a popup

julienGrd opened this issue · comments

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Hello guys, In my blazor server app I need to handle a client OpenId authentication.
The openid provider is private and i cant make any modificaiton on it.

i see many example on how it works in blazor server app, but all of them make the user leave the app to deal with the OpenId provider page, and then the app is call back.

I would like to manage that in a popup to prevent my user leave my app. Because a lot of thing are loaded in memory (and i can't change this part because all the app was built on that il would bee too many works)

i was able to run that in a popup and close the popup with the callback, but it seem until the page is reload the app don't have his authentication state updated (AuthenticationStateProvider.GetAuthenticationStateAsync(); is empty).

If i just reload the page iAuthenticationStateProvider.GetAuthenticationStateAsync(); return a connected user

Can you confirm this behavior or it should work ?

Do you have any workaround to make this works ? (im OK if i don't have all the mecanism of identity, at the end i just want to retrieve a token, so it can be for example save this token on the server side in a dictionnary, maybe by using the state field used on the openid request to identify my clients)

Thanks for your help !

Expected Behavior

Be able to access user connected without reloading the app

Steps To Reproduce

  • open the oiddc workflow in a popup and connect
  • check the authentication state in a component in the app, its not set connected until the page is reload

Exceptions (if any)

No response

.NET Version

6.0.400-preview.22301.10

Anything else?

No response

@julienGrd thanks for contacting us.

There is no way to accomplish this on Blazor Server. Blazor Server apps don't deal with authentication, the authentication happens before the browser starts the connection to the server.

The only way I could think you can accomplish this is if you implement an endpoint to trigger the auth and use JS interop to open a popup to that endpoint and update the authentication state on the Blazor app with your own AuthenticationStateProvider implementation.

@javiercn thanks for the answer, can you be more specific about the workaround ?

Because this feature is a must go in our app.

I was even thinking of implement the OpenId flow by myself but it would be a shame

thanks !

@julienGrd Authentication in Blazor Server works like it does in a regular MPA (Razor Pages/MVC).

sequenceDiagram
rect azure
note over Browser: Browser authenticates with the Identity Provider
Browser->>Server: GET /
Server-->>Browser: 302 https://idp.com/authorize?clientId=abcde
Browser->>IdPAuth: GET https://idp.com/authorize?clientId=abcde
IdPAuth-->>Browser: 302 /callback?code=fdsa
Browser->>Server: GET /callback?code=fdsa
Server->>IdPAuth: POST /token[code=fdsa]
IdPAuth-->>Server: [access_token=eyasdf, refresh_token=rtblob]
Server-->>Browser: 302 / [set-cookie:AuthCookie[Alice]]
end
rect HoneyDew
note over Browser: Blazor host page is rendered
Browser->>Server: GET / [cookie=AuthCookie[Alice]]
Server-->>Browser: 200 [html]
end
rect Ivory
note over Browser: Blazor server application starts
Browser->>Server: GET /_blazor/connect[cookie=AuthCookie[Alice]]
Server-->>Browser: 101 [Websocket][User=Alice]
loop Websocket messages
Browser->>Server: Start
Server-->>Browser: Render root components
Browser->>Server: User click, type, etc
Server-->>Browser: Render
end
end
Loading

Blazor Server uses the identity established when you connect to the circuit to authenticate the user and keeps that identity while the websocket connection is alive. Authentication happens before the Blazor application starts.

Blazor Server establishes the identity by default when the app connects to the websocket. If you want to use a pop up, I imagine you want to authenticate the user after the Blazor app has started, and in that case, you need to implement your own AuthenticationStateProvider and figure out a safe way to establish and update the user identity.

The only way that I can think of performing the flow with Microsoft.AspNetCore.Authentication.OpenIdConnect involves launching a popup with JS interop to and endpoint in your server to trigger the flow, and then do additional work to refresh the authenticated user based on the new info.

actually i have a pretty good idea on how to implement that if i re-implement the flow by myslft, but by using the connectors i don't know where tu put my code by using the AddOpenIdConnect.

Indeed i don't have the control on the different callback which are called

I have some event launch where it seem i can retrieve the token, user infos, etc

OnTokenValidated = context =>
            {

                return Task.FromResult(0);
            },
            OnAuthorizationCodeReceived = (context) =>
            {
                return Task.FromResult(0);
            },
            OnTokenResponseReceived = (context) =>
            {
                return Task.FromResult(0);
            },

but at this place, i dont know how to deal with AuthenticationStateProvider,

the total code look like that now

//the javascript method will open the link in a popup
<a class="btn btn-primary" onclick="geckos.appManager.redirectToOIdc('/public/oidc/login?redirectUri=@RedirectUriOidc')">S'identifier avec OpenID</a>

On the controller side
AuthenticationStateProvider _authenticationStateProvider;
        public LoginOidcController(AuthenticationStateProvider pAuthenticationStateProvider)
        {
            _authenticationStateProvider = pAuthenticationStateProvider;
        }

        [HttpGet("/public/oidc/login/")]
        public ActionResult Login(string redirectUri)
        {
            return new ChallengeResult("oidc", new AuthenticationProperties { RedirectUri = "/public/oidc/logincallback/" });
            //return new ChallengeResult("oidc", new AuthenticationProperties { RedirectUri = redirectUri });
        }

        [HttpGet("/public/oidc/logincallback/")]
        public async Task<ActionResult> OidcLoginCallback()
        {
           // var l = await _authenticationStateProvider.GetAuthenticationStateAsync();//i was thinking maybe i can retrieve some infos here but its not set
            //return new JavaScriptResult("window.close();");
            return Content("<script>window.close();</script>", "text/html");//this close the popup
            // Render output script to close the popup window
            //    return Content(@"
            //<script>

            //</script>
            // ");
        }

You can't use the AuthenticationStateProvider outside blazor. You need to pass back the results from your pop-up to your Blazor Server app using a browser api like POST message, and then send that to the server via JS interop.

And secure the transfer along the way.

@javiercn OK i think i begin to understand

  • in the popup i retrieve the AuthenticationState which is correctly set, the user, also access token, referesh token this kind of things
  • i encrypt them and send it through js
  • in the js, i call window.opener.postMessage(myObj) and then window.close();
  • in the js also, the parent was notified like that
window.addEventListener('message', e => {
    const key = e.message ? 'message' : 'data';
    const data = e[key];

//work with authentication data, send it to a component
    // ...
}, false);

@julienGrd something like that.

That said, I think there are likely additional security concerns that you will have to deal with.

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.