0

I have a Blazor server side app that calls an authentication server. I own the auth server (also an ASP.NET Core WebApp) and it is based on OpenIdDict. I can successfully generate an authorization code as I have tested it with oidcdebugger.com.

The auth server redirects back to the app so that app can complete the authorization code flow and the user can access the app.

The first thing I did was the following in MainLayout.razor:

@inherits LayoutComponentBase

<AuthorizeView>
    <NotAuthorized>
        <GetAuthorizationCode/>
    </NotAuthorized>
    <Authorized>
        <Header/>
        <NavMenu/>  
    </Authorized>
</AuthorizeView>

@code {

}

GetAuthorizationCode is a simple component which is:

public class GetAuthorizationCode: ComponentBase
{
    [Inject]
    protected IAuthService AuthService { get; set; }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        AuthService.GetAuthorizationCode();
    }
}

And the service prepares the request to be sent to the auth server:

public void GetAuthorizationCode()
{
    var url = "urltoauthserver";

    var parameters = new Dictionary<string, string>()
    {
        { "client_id", "hereclientid" },
        { "redirect_uri", "hereredirecturi" },
        { "scope", "openid email profile" },
        { "response_type", "code" },
        { "response_mode", "form_post" },
        { "state", Guid.NewGuid().ToString() },
        { "nonce", "herenonce" }
    };

    url = QueryHelpers.AddQueryString(url, parameters);
    navigationManager.NavigateTo(url);
}

The redirect uri is an api controller within the Blazor app itself (not an another external api). I need to use an api controller because the redirect uri is a POST and looks like this:

[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class LoginController : ControllerBase
{
    [HttpPost]
    public IActionResult Callback([FromForm] string code, [FromForm] string state)
    {
        return Ok($"Login callback. Access token: {code}");
    }
}

The flow should be that once the auth server authenticates the user and the authorization code is sent back to the redirect uri, the action above should create a request with the authorizatoin code to send to the auth server and receive an access token.

In startup.cs I also configured OpenIdConnect as follows:

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    options.ClientId = "hereclientid";
    options.ClientSecret = "hereclientsecret";
    options.RequireHttpsMetadata = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.AuthenticationMethod = OpenIdConnectRedirectBehavior.RedirectGet;
    options.Authority = "authserver";
    options.Scope.Add("email");
    options.Scope.Add("profile");
    options.SecurityTokenValidator = new JwtSecurityTokenHandler
    {
        InboundClaimTypeMap = new Dictionary<string, string>()
    };
    options.TokenValidationParameters.NameClaimType = "name";
});

The problem is that the action is never reached because during redirection the GetAuthorizationCode is called over and over again.

EDIT The endless loop occurs if the redirect is a GET. If the redirect is a POST, I get a 400 Bad Request and this is the log:

2021-02-04 16:56:19.628 +01:00 [INF] Request starting HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91 2021-02-04 16:56:19.628 +01:00 [INF] CORS policy execution successful. 2021-02-04 16:56:19.629 +01:00 [INF] Executing endpoint '/_Host' 2021-02-04 16:56:19.629 +01:00 [INF] Route matched with {page = "/_Host", action = "", controller = ""}. Executing page /_Host 2021-02-04 16:56:19.639 +01:00 [INF] Antiforgery token validation failed. The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken". Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery request token was not provided in either form field "__RequestVerificationToken" or header value "RequestVerificationToken". at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext) at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context) 2021-02-04 16:56:19.671 +01:00 [INF] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter'. 2021-02-04 16:56:19.674 +01:00 [INF] Executing HttpStatusCodeResult, setting HTTP status code 400 2021-02-04 16:56:19.674 +01:00 [INF] Executed page /_Host in 45.2108ms 2021-02-04 16:56:19.674 +01:00 [INF] Executed endpoint '/_Host' 2021-02-04 16:56:19.675 +01:00 [INF] Request finished HTTP/2 POST https://localhost:44379/api/Login/Callback application/x-www-form-urlencoded 91 - 400 - - 47.2169ms

I added [IgnoreAntiForgeryToken] for the POST action but it doesn't work

Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263

1 Answers1

0

The component is rendered when the user tries to access a resource protected by the Authorize attribute. Code in the component navigates to an external authentication server which I reckon produces authorization code, whatever it is...

The external authentication server should redirect back to the calling code, passing it the authorization code... That means that you should call the external url with the address ( returnUrl) you want him to send back authorization code. I'm not sure what you mean by authorization code, but say it is a Jwt token; thus you should have code in your GetAuthorizationCode (usually done in services rather than components) that accept the Jwt token, and store it in the local Storage (for future use). Remember, the user is trying to access a protected resource, say he's trying to access the FetchData page...in that case code in the FetchData page should read the Jwt token (authorization code) from the local Storage and add it to the Authorization header of the HttpRequestMessage object that is used to request data from the Web Api end point. Your Web Api end point must be protected with the Authorize attribute that calls the authorization service to check if the user is authorized to access this resource...

The external url should never re-direct to the Web Api end point. It should redirect to the code that called it, passing it authorization code (Jwt token). After getting this code your Blazor code may call the Web Api end point passing it the authorization code (Jwt token)

UPDATE 1:

This code: navigationManager.NavigateTo(url);

I believe should be: navigationManager.NavigateTo(url, forceLoad:true);

Note: The second parameter force Blazor to navigate out of the SPA space. Try the code above and report of changes... This may prove to be meaningful.

Note: The following are links to my answers regarding OIDC, server-side Blazor, etc. I'd suggest you to read them, copy code from them, etc. You can gain a lots from them, as they were all tested by me. There are many answers by me that are related to authentication, authorization in Blazor, look for them and help yourself. They can make your life more bearable in this jungle...

See this and this and this

enet
  • 41,195
  • 5
  • 76
  • 113
  • I'm using authorization code flow and the first callback returns the code which then I need to exchange for a token. The component simply calls a service which builds the request and calls navigateto. I never reach the callback because of the endless loop – Ivan-Mark Debono Feb 04 '21 at 02:29
  • Do you mean that the API controller produces the token in exchange for the authorization code ? If so, the Api controller should authenticate the user, generate the token, and then pass it back to the Blazor calling code. That means, that the external authentication server should pass the return Url to the Web Api. Note: If you stop talking in riddle, and explain in details what you have and what you want to achieve life would be easier for all involved. Can't you provide more information, as for instance, about the external authentication server, what is it, who owns it, etc. – enet Feb 04 '21 at 02:53
  • You've never used the word token before I 've used it... Do you mean Jwt token ? Is the Web Api owned by you ? Do you have code there that authenticate the user, and generates Jwt token ? etc. ? Speak in details, please... – enet Feb 04 '21 at 02:53
  • I edited the question hopefully to your satisfaction. – Ivan-Mark Debono Feb 04 '21 at 03:22