OpenID Connect is an authentication protocol, built on top of OAuth 2.0, that can be used to securely sign users in to web applications.
This authentication protocol allows you to perform SSO (single sign-on). It introduces the concept of an ID token, which allows the client to verify the identity of the user and obtain basic profile information about the user. Since, it extends OAuth 2.0, it enables clients to securely retrieve access tokens.
OpenID Connect is recommended for securing browser applications. For mobile applications, OAuth authorization code flow is preferred
ASP.NET Core Identity supports adding login functionality to ASP.NET Core apps via external/social login providers like Google, Twitter, Identity Server along with typical in-house persistent stores for users accounts or cloud based Azure Active Directory
In this article, we’ll see how to add external provider based login functionality to ASP.NET Core web app by creating a simple sample app talking to generic Open ID Provider (OP) typically, an Authorization server that implements the OpenID Connect specification. To authenticate end users with OpenID Connect, we will use two middlewares in a chain: Cookie and OpenIdConnect. The former is responsible for managing encryption/decryption of the AuthenticationTicket and its storage into a cookie, whereas the latter takes care of challenging web application (client) for credentials and verification, besides parsing the requested claims. cookies makes most sense in a state less web application. When we combine the Cookie and OpenIdConnect middlewares, the user information retrieved from the ID Token becomes an AuthenticationTicket, which is encrypted and stored in a cookie.
As shown below, we created a very light weight ASP.NET Core 2.2 web app with just one controller/view to demonstrate sign-in and sign-out functionality built using Open ID provider typically used in enterprises.
This article doesn’t go into OAuth 2.0 workflows. It’s beyond the scope of this article. We’re skipping other relevant things like how to configure callbacks to your app, client registration with provider (Client ID and Client secret. This article only focuses on implementation using Open ID and Cookie Auth middle wares in .Net Core 2.0+
OpenId Connect and Cookie Auth middlware configuration in startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using OidcLoginApp.Helpers; namespace OidcLoginApp { public class Startup { protected readonly ILogger<Startup> Logger; protected readonly IHostingEnvironment HostingEnvironment; public Startup(IConfiguration configuration, IHostingEnvironment hostingEnvironment, ILogger<Startup> logger) { Configuration = configuration; HostingEnvironment = hostingEnvironment; Logger = logger; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options => { // Cookie settings options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(5); options.LoginPath = "/Home/Index"; //login page options.SlidingExpiration = true; }).AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.CallbackPath = "/OidcSignin"; options.Authority = Configuration["authority"]; //OpenID provider authority URL options.ClientId = Configuration["clientid"]; options.ClientSecret = Configuration["secret"]; options.SignedOutRedirectUri = "/Home/Login"; options.SaveTokens = true; //access tokens are stored in cookie. For real world scenarios, this is discouraged // Configure the scope options.Scope.Clear(); options.Scope.Add("openid"); //scopes options.Scope.Add("profile"); options.Scope.Add("email"); options.ResponseType = OpenIdConnectResponseType.CodeIdToken; //includes id token and access token options.Events = new OpenIdConnectEvents() { OnAuthenticationFailed = context => { context.HandleResponse(); context.Response.StatusCode = 500; context.Response.ContentType = "text/plain"; if (!HostingEnvironment.IsProduction() || !HostingEnvironment.IsStaging()) { // Debug only, in production do not share exceptions with the remote host. return context.Response.WriteAsync(context.Exception.ToString()); } return context.Response.WriteAsync( "An error occurred processing your authentication. please contact administrator"); }, //Force scheme of redirect URI (THE IMPORTANT PART) -- to overcome occasional correlation errors due to mismatched redirect_uri http scheme - which can happen because load balancers handle SSL sometimes forwarded headers don't reach OnRedirectToIdentityProvider = redirectContext => { Logger.LogInformation("redirecting to OpenID provider"); redirectContext.ProtocolMessage.RedirectUri = redirectContext.ProtocolMessage.RedirectUri.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase); return Task.CompletedTask; }, //nice handler to capture any intermittent unhandled remote login failures - unable to match key errors or correlation errors OnRemoteFailure = context => { Logger.LogError(context.Failure.Message, context.Failure.InnerException); context.HandleResponse(); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; context.Response.Redirect("/Home/Error"); return Task.CompletedTask; }, //fired when remote SSO session is also killed OnRedirectToIdentityProviderForSignOut = (context) => { context.Response.Redirect(Configuration["end_session_endpoint"]); context.HandleResponse(); return Task.CompletedTask; } }; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } } |
Home Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Mvc; using OidcLoginApp.Models; namespace OidcLoginApp.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } public IActionResult Privacy() { return View(); } [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } /// <summary> /// login challenge to OIDC provider /// </summary> [HttpGet] public Task Challenge(string provider, string returnUrl) { return HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties() { RedirectUri = Url.Action("Index", "Home") }); } /// <summary> /// </summary> /// <returns></returns> public async Task<IActionResult> Logout() { //app sign out await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index"); //uncomment to force remote SSO sign out //await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties //{ // RedirectUri = Url.Action("Index", "Home") //}); } } } |
Index View
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@using Microsoft.AspNetCore.Http @{ ViewData["Title"] = "Home Page"; } <div class="text-center"> <h1 class="display-4">Welcome</h1> @if (!ViewContext.HttpContext.User.Identity.IsAuthenticated) { <a class="btn btn-primary" asp-controller="Home" asp-action="Challenge">External Provider Login (OIDC)</a> } else { <h1>Hello, Authenticated User!!</h1> <h3>Claims</h3> <dl> @foreach (var claim in ViewContext.HttpContext.User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl> <a class="btn btn-primary" asp-controller="Home" asp-action="Logout">External Provider Logout (OIDC)</a> } </div> |

Login page for unauthenticated user

Claims page after successful authentication
You can download source code using below link