Implement OIDC in ASP.NET Framework with Azure - 3


Last Updated: 7/13/2026

ASP.NET Framework OIDC and Windows Authentication

  1. 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;
                    }
                }
            });
        }
    }
}
  1. 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);
        }
    }
}