ASP.NET Framework OIDC and Windows Authentication
- Startup.cs
using System;
using System.Configuration;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Owin;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
[assembly: OwinStartup(typeof(AspnetFrameworkAzureOIDCSample.Startup))]
namespace AspnetFrameworkAzureOIDCSample
{
public class Startup
{
private static readonly string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static readonly string ClientSecret = ConfigurationManager.AppSettings["ida:ClientSecret"];
private static readonly string Authority = ConfigurationManager.AppSettings["ida:Authority"];
private static readonly string RedirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
public void Configuration(IAppBuilder app)
{
// 1. Setup local encrypted session cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// This is the target string identifier
AuthenticationType = "OidcCookie",
CookieName = "SearchPage_Oidc",
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager(),
CookieSameSite = Microsoft.Owin.SameSiteMode.Lax,
CookieSecure = CookieSecureOption.Always
});
// 2. Configure Azure AD OIDC
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
AuthenticationType = "AzureADOpenId",
ClientId = ClientId,
ClientSecret = ClientSecret,
Authority = Authority,
RedirectUri = RedirectUri,
ResponseType = OpenIdConnectResponseType.Code,
Scope = "openid profile offline_access", // offline_access yields refresh tokens
SignInAsAuthenticationType = "OidcCookie",
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager(),
RedeemCode = true,
UsePkce = true,
Notifications = new OpenIdConnectAuthenticationNotifications
{
SecurityTokenValidated = async context =>
{
var identity = context.AuthenticationTicket.Identity;
// Capture tokens safely after validation completes
var accessToken = context.ProtocolMessage.AccessToken;
if (!string.IsNullOrEmpty(accessToken))
{
identity.AddClaim(new Claim("access_token", accessToken));
}
var idToken = context.ProtocolMessage.IdToken;
if (!string.IsNullOrEmpty(idToken))
{
identity.AddClaim(new Claim("id_token", idToken));
}
// Map Azure unique identifier if desired
var objectId = identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
if (!string.IsNullOrEmpty(objectId))
{
identity.AddClaim(new Claim("azure_user_id", objectId));
}
context.OwinContext.Authentication.SignIn(
context.AuthenticationTicket.Properties,
identity
);
// 2. CRITICAL FIX: Tell OWIN to immediately write headers to the response stream
// instead of waiting until the end of the entire request lifecycle.
context.OwinContext.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate");
context.HandleResponse();
// 3. DYNAMIC FIX: Read the RedirectUri that was set during the Controller's Challenge
string dynamicRedirectUrl = context.AuthenticationTicket.Properties.RedirectUri;
// Fallback safety check: If for some reason it is empty, default to home page
if (string.IsNullOrEmpty(dynamicRedirectUrl))
{
dynamicRedirectUrl = "/";
}
// 4. Execute the dynamic redirect
context.Response.Redirect(dynamicRedirectUrl);
//context.Response.Redirect("/Order/Index");
await Task.CompletedTask;
},
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Home/Error?message=" + context.Exception.Message);
return Task.CompletedTask;
}
}
});
}
}
}
- Protected Page
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.Mvc;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
namespace AspnetFrameworkAzureOIDCSample.Controllers
{
public class OrderController : Controller
{
// GET: Order
public ActionResult Index()
{
// 1. Get the OWIN Authentication Manager
IAuthenticationManager authManager = HttpContext.GetOwinContext().Authentication;
// 2. Try to read your passive OIDC cookie
// (Ensure this name matches the AuthenticationType set in your Startup.Auth.cs)
AuthenticateResult oidcResult = authManager.AuthenticateAsync("OidcCookie").Result;
// 3. If the OIDC cookie doesn't exist, challenge the user to log into the external OIDC provider
if (oidcResult == null || oidcResult.Identity == null)
{
// Capture the route dynamically
string currentController = RouteData.Values["controller"].ToString();
string currentAction = RouteData.Values["action"].ToString();
// Build the local return path (e.g., "/Home/Index")
//string returnUrl = Url.Action(currentAction, currentController, new { orderId = 1 });
string returnUrl = "/Order?id=1";
//string returnUrl = "/";
// Send challenge to Azure AD middleware
HttpContext.GetOwinContext().Authentication.Challenge(
new AuthenticationProperties { RedirectUri = returnUrl },
"AzureADOpenId"
);
return new HttpUnauthorizedResult();
}
// 4. If code reaches here, the OIDC validation succeeded!
var accessToken = oidcResult.Identity.FindFirst("access_token");
var idToken = oidcResult.Identity.FindFirst("id_token");
return Content("Order Page, accessToken:" + accessToken + ", idToken:" + idToken);
}
}
}