2
votes

ASP.NET Core 3.1 using Microsoft.AspNetCore.Authentication.OpenIdConnect in an implicit flow

I am attempting to execute an OpenIDConnect implicit flow. It seems to work okay until the callback when I get the following error:

Exception: OpenIdConnectAuthenticationHandler: message.State is null or empty.

Now I suspect it's because my code can't get the State and other parameters because they are behind a hash, or URL fragment. In the browser location window I see https://localhost:44300/signin-oidc#id_token=eyJ0&State=etc. etc. (note the hash).

I understand that in implicit flows the tokens are placed behind the hash and that can be read with javascript like in Angular apps or whatnot. But I also thought response_mode=form_post would cause the authorization endpoint to POST to the callback. However, in my case I doesn't appear the authorization endpoint is honoring that or something is going wrong. Here is my F12 log:

Name                Url            
localhost           localhost (me)      GET 302 
auth?client_id=     authority           GET 302
auth?client_id=     web client auth     GET 200
signonCallback      authority           POST 302
signin-oidc         localhost(me)       GET 500

In frustration I spun up IdentityServer4 locally to test implicit flows and it posts back. Not sure what's different here in the real world. There's probably a lot different but how can I cope using ASP.NET Core constructs and not resorting to some page that pulls the hash from the location bar with javascript?

Code:

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
    options.AccessDeniedPath = new PathString("/Authorization/AccessDenied");
})   
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ResponseType = OpenIdConnectResponseType.IdToken; //id_token
    options.ResponseMode = OpenIdConnectResponseMode.FormPost; //form_post

    options.Authority = Configuration["MyApp:Authentication:Authority"];
    options.GetClaimsFromUserInfoEndpoint = true;
    options.ClientId = Configuration["MyApp:Authentication:ClientId"];
    options.CallbackPath = 
        new PathString(Configuration["MyApp:Authentication:CallbackPath"]);
    options.SignedOutCallbackPath = 
        new PathString(Configuration["MyApp:Authentication:SignedOutCallbackPath"]);
    options.Scope.Clear();
    options.Scope.Add("openid");
    options.SaveTokens = true;
});
1

1 Answers

0
votes

In the implicit flow, use the fragment option when you want your JavaScript application to use/access the tokens directly. And you use the form_post option when you want the tokens to be sent back to the backend (ASP.NET).

If you can you should try to use the authorization code flow instead because it is more safe.

If you get the error State is null or empty, then that means that the initial authentication request lacked a state parameter. The state parameter is a security parameter and basically the client must set it with a random value and when you get the token the client should check if that state is the same.

When you use AddOpenIdConnect, you want it to handle everything, including creating the initial request to your openid-connect server.