.net 6 web api to OneLogin OIDC challengeasync results in Script is Disabled

115 Views Asked by At

My question is this: based on the following setup, why am I getting a script is disabled? What am I doing wrong here? Thanks in advance.

Here is the setup: I have an ASP.NET Core 6 Web API with a controller that inherits from ControllerBase. This server is meant to act as an authentication server. We are using OneLogin as our OIDC IdP. We are using Microsoft.AspNetCore.Authentication.OpenIdConnect for this, rather than a third party library (please excuse me if my terminology is incorrect. I am a newbie at this).

Note that I can hit a breakpoint in the Challenge method of the controller, but OneLogin never hits the callback method.

When I call ChallengeAsync in the controller, I get the following display in dev tools/network:

<html>
    <head>
        <title>Working...</title>
    </head>
    <body>
        <form method="POST" name="hiddenform" action="https://openid-connect.onelogin.com/oidc/2/auth">
            <input type="hidden" name="client_id" value="a191ac20-4ce8-013c-239a-7a4043bb707b232913"/>
            <input type="hidden" name="redirect_uri" value="http://localhost:21197/oidcauth/callback/"/>
            <input type="hidden" name="response_type" value="code"/>
            <input type="hidden" name="scope" value="openid profile"/>
            <input type="hidden" name="code_challenge" value="1rTHYLlKRSFOR0bF2UcRFA7xwHIbRs1FLI5jqdjHwhY"/>
            <input type="hidden" name="code_challenge_method" value="S256"/>
            <input type="hidden" name="response_mode" value="form_post"/>
            <input type="hidden" name="nonce" value="638335051762872899.MGYwMjMyNzEtMTNmZC00YTMwLTkxMDItMDVmNmE0NDlkZjZhNTU0MjcyYTUtYjA3NC00MzAwLWI4YzEtODFiYzkzZGRjODU1"/>
            <input type="hidden" name="state" value="CfDJ8GzhTZwxN6ZJvOF4eiYmv7uE-eZ2daz8RxKi9QKeDklohwRfRozxj6vD3FiOmUh3cB2_e1AWBkTFV0MiPLMu7UHmVVEcO12o6iyAeopTHq0TKHmM6JL1sKLk47a2U8CY6v5PG2WQcx4QmcqQ77VkVItx1c46gxpaX7sKh_wNyv1jA9Tc4AvO_r_8m_j8MkSU8Tq_PIfSDokErCTI8ZdaAeJPu3fc6ITeW_z2bs-FwoGt4cYcTTvWvzKh1p8cfXGlfbV1kb8w1Ki8bOkhWA3BrE9z7tqMSXN4vtEEGOJuR_OBp4LdHRGoBf-oZHf7CFqq_56W1sr7RWFKafNAo1K-_MjtoCdvID8E2WAyMOo9hRf_QNy7KdRpPcnusMAxuF_HiWGZ1EUGxcZYlRo8F8Jz17yXxPSy7_cdq1v9oKL_tSMU"/>
            <noscript>
              **  <p>Script is disabled. Click Submit to continue.</p>**
                <input type="submit" value="Submit"/>
            </noscript>
        </form>
        <script language="javascript">
            window.setTimeout(function() {
                document.forms[0].submit();
            }, 0);
        </script>
    </body>
</html>

This is my controller:

    [HttpGet]
    [Route("challenge")]
    public async Task Challenge(string provider, string returnUrl)
    {
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

        await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme,
            new OpenIdConnectChallengeProperties { RedirectUri = returnUrl });
    }

    [HttpGet]
    [Route("callback")]
    //[EnableCors("oidc_callback")]
    public async Task<IActionResult> Callback()
    {
        var result = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
        var returnUrl2 = result?.Properties?.Items["returnUrl"] ?? "~/";

        var claims = HttpContext.User.Claims;
     var usrok = accountService.DoesUserHaveAccessToApplication(appusr, "MyApp"))

        return Ok(new { returnUrl = "/", User = "TestUsr" });
    }

An extension method to configure my authentication:

using Mhk.Authenticate.Server.Contracts;
using Mhk.Authenticate.Server.Models;
using Mhk.Authenticate.Server.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OAuth.Claims;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Serilog;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;

namespace Mhk.Authenticate.Server.Extensions
{
    public static class OIDCServicesExtension
    {
        public static void ConfigureOIDCMiddlewareExtension(this IServiceCollection services,
            IConfiguration configuration)
        {
            var tokenValidationClaimsService = services.BuildServiceProvider().GetRequiredService<ITokenValidationAdditionalClaimsService>();
            var authServerConfig = configuration.GetSection("AuthServerConfig").Get<AuthServerConfig>();
            var ssoconfig = configuration.GetSection("SSOConfig").Get<SSOConfig>();

            var oidcConfig = ssoconfig?.oidc;
            if (oidcConfig == null) { throw new ArgumentNullException("SSOConfig.oidc not found"); }

            services.Configure<CookiePolicyOptions>(options =>
            {
                options.MinimumSameSitePolicy = SameSiteMode.None;
                options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
                options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions);
            });
         
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
            })
            .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
            {
                //options.Cookie.SameSite = SameSiteMode.None;//?TODO: Is this correct?
                options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
                //options.LoginPath = "/oidcauth/challenge";

            })
            .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
            {
                options.Authority = oidcConfig.MetadataAddress;
                options.ClaimsIssuer = "MyAuthenticateIssuer";

                options.ClientId = oidcConfig.ClientId;
                //options.ClientSecret = oidcConfig.ClientSecret;
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.UsePkce = true;
                options.AuthenticationMethod = OpenIdConnectRedirectBehavior.FormPost;
                options.ClaimActions.Add(new IssuerFixupAction());

                // This is the key setup to ensure IdP calls back to the /callback endpoint
                options.CallbackPath = "/oidcauth/callback/";
                var scopes = oidcConfig.Scope;// configuration.GetSection("oidc:scope").GetChildren().Select(c => c.Value).ToArray();

                if (scopes != null)
                {
                    options.Scope.Clear();

                    foreach (var scope in scopes)
                    {
                        options.Scope.Add(scope);
                    }
                }

                options.Events = new OpenIdConnectEvents
                {
                    OnRedirectToIdentityProvider = async ctx =>
                    {
                        Log.Debug(ctx.ProtocolMessage.RedirectUri);
                    },
                    OnMessageReceived = async ctx =>
                    {
                        Log.Debug(ctx.Options.Authority);
                    },
                    OnTokenValidated = async ctx =>
                    {
                        Log.Debug("OnTokenValidated");
                        await tokenValidationClaimsService.ValidateTokenAsync(ctx);
                    },
                    OnUserInformationReceived = async ctx =>
                    {
                        Log.Debug(ctx.User.RootElement.ToString());
                    }
                };

                //options.Validate();
            })
            .AddJwtBearer(options =>
            {
                var cert = X509Helper.GetCertificateByThumbprint(authServerConfig.X509CertificateThumbprint);

                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new X509SecurityKey(new X509Certificate2(cert))

                    // ... other token validation parameters like ValidateIssuer, ValidateAudience, etc.
                };
            });
        }

        private static void CheckSameSite(HttpContext httpContext, CookieOptions options)
        {
            if (options.SameSite == SameSiteMode.None)
            {
                var userAgent = httpContext.Request.Headers["User-Agent"].ToString();

                if (DisallowsSameSiteNone(userAgent))
                {
                    options.SameSite = SameSiteMode.Unspecified;
                }
            }
        }

        // TODO: Use your User Agent library of choice here.
        public static bool DisallowsSameSiteNone(string userAgent)
        {
            if (string.IsNullOrEmpty(userAgent))
            {
                return false;
            }

            // Cover all iOS based browsers here. This includes:
            // - Safari on iOS 12 for iPhone, iPod Touch, iPad
            // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad
            // - Chrome on iOS 12 for iPhone, iPod Touch, iPad
            // All of which are broken by SameSite=None, because they use the iOS networking stack
            if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12"))
            {
                return true;
            }

            // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes:
            // - Safari on Mac OS X.
            // This does not include:
            // - Chrome on Mac OS X
            // Because they do not use the Mac OS networking stack.
            if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") &&
                userAgent.Contains("Version/") && userAgent.Contains("Safari"))
            {
                return true;
            }

            // Cover Chrome 50-69, because some versions are broken by SameSite=None, 
            // and none in this range require it.
            // Note: this covers some pre-Chromium Edge versions, 
            // but pre-Chromium Edge does not require SameSite=None.
            if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6"))
            {
                return true;
            }

            return false;
        }

        private class IssuerFixupAction : ClaimAction
        {
            public IssuerFixupAction() : base(ClaimTypes.NameIdentifier, string.Empty) { }

            public override void Run(JsonElement userData, ClaimsIdentity identity, string issuer)
            {
                var oldClaims = identity.Claims.ToList();

                foreach (var claim in oldClaims)
                {
                    identity.RemoveClaim(claim);
                    identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, claim.OriginalIssuer, claim.Subject));
                }
            }
        }
    }
}

My program file:

using MyExample.Authenticate.Server.Contracts;
using MyExample.Authenticate.Server.Extensions;
using MyExample.Authenticate.Server.Models;
using MyExample.Authenticate.Server.Repository;
using MyExample.Authenticate.Server.Services;
using MyExample.Authenticate.Server.Services.InternalAuth;
using MyExample.Authenticate.Server.Services.OIDCAuth;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Serilog;
using Serilog.Context;
using System.Security.Claims;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var authServerConfig = builder.Configuration.GetSection("AuthServerConfig").Get<AuthServerConfig>();

builder.Services.AddSingleton<IAuthServerConfig>(authServerConfig);
builder.Services.AddSingleton<IOidcConfig, OidcConfig>();
builder.Services.AddSingleton<ISamlConfig, SamlConfig>();
builder.Services.AddSingleton<ISSOConfig, SSOConfig>();
builder.Services.AddScoped<IRoleClaimService, RoleClaimService>();
builder.Services.AddScoped<IJwtTokenService, JwtTokenService>();
builder.Services.AddTransient<ILoginResultBuilder, LoginResultBuilder>();
builder.Services.AddTransient<ISecurityTokenOptions, SecurityTokenOptions>();
builder.Services.AddSingleton<ISecurityOptions, SecurityOptions>();
builder.Services.AddSingleton<ISecuritySettings, SecuritySettings>();
builder.Services.AddSingleton<ICustomAppSettings, CustomAppSettings>();
builder.Services.ConfigureDatabase(builder.Configuration);

builder.Services.AddScoped<IAccountService, AccountService>();
builder.Services.AddScoped<ITokenValidationAdditionalClaimsService, TokenValidationAdditionalClaimsService>();

Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger();

//These are setups for Microsoft.AspNetCore.Authentication.OpenIdConnect
//Put in separate extension method...
builder.Services.ConfigureOIDCMiddlewareExtension(builder.Configuration);

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin()
        .AllowAnyHeader()
        .AllowAnyMethod();
    });
});

var app = builder.Build();

var isDev = app.Environment.IsDevelopment();

if (isDev)
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseCookiePolicy();

app.UseCors(); // Ensure CORS middleware is after the custom middleware
app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapDefaultControllerRoute();
});

app.MapControllers();

app.Run();

I have tried what is described above. I have tried other configurations, but this is the one that gets me the closest to success.

0

There are 0 best solutions below