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.