Skip to main content
Skip table of contents

.NET Framework 4.5+ MVC OpenID Connect example 

Last updated: April 2025 
.NET Framework version tested: 4.7.2 

This tutorial takes you through the minimum steps needed to integrate a .NET MVC application with OpenAthens Keystone using OpenID Connect. It is assumed that you have experience of developing applications using ASP.NET in Visual Studio. 

Goal in this example 

Authenticate a user and display all the received claims on a page. In the real world you would feed the claims into your authorisation / user-session management process, so that you can grant access only to your customers. 

Instructions 

Create project 

Create a new project in Visual Studio, selecting the template “ASP.NET Web Application (.NET  Framework)”. At the second stage, select the MVC template option, and enable the “Configure for HTTPS” option if available. Don’t select any authentication options at this stage. 

If “Configure for HTTPS” was not available in your version of the Visual Studio template, configure it manually: 

  1. In the solution explorer, select the project folder, then in the properties pane, change SSL Enabled to true. Copy the SSL URL that is created. 

  1. Right click the project and select properties, and in the Web tab, paste the https URL copied above into the Project Url field in place of the existing http URL. 

Create connection in OpenAthens 

Create a new OpenID Connect Application record in the OpenAthens Service Provider dashboard, using the SSL URL from the project as the Application URL (but with no trailing /). Copy the same URL (including the port number) to the Redirect URL, this time include the trailing /. 

Add the Owin and OpenID Connect dependencies 

Add the following packages:  

  • Microsoft.AspNet.Identity.Owin 

  • Microsoft.Owin.Host.SystemWeb 

  • Microsoft.Owin.Security.OpenIdConnect 

The OpenIdConnect package must be version 4.1.0 or later, to get support for the OpenID Connect Code flow, which OpenAthens uses. 

Configuration 

Add the following appSettings to Web.config: 

CODE
<add key="oidc:Authority" value="https://connect.openathens.net" /> 

<add key="oidc:ClientId" value="{clientId from the OIDC application record created in the publisher dashboard}" /> 

<add key="oidc:ClientSecret" value="{clientSecret from the OIDC application created in the publisher dashboard}" /> 

<add key="oidc:RedirectUrl" value="{redirectUrl set in the publisher dashboard}" /> 
 

(You should keep the ClientSecret private. In the real world you would keep it out of source control and load it separately, from a file outside the application root or from an environment variable.) 

Add a Startup class that initialises the OpenID Connect authentication via an Owin assembly binding. Following the convention used in other samples from Microsoft, this is a partial class split across two files. Firstly, Startup.cs in the project root: 

CODE
[assembly: OwinStartup(typeof(<YourProjectName>.Startup))] 

namespace <YourProjectName> 

{ 

    public partial class Startup 

    { 

        public void Configuration(IAppBuilder app) 

        { 

            ConfigureAuth(app); 

        } 

    } 

} 
 

Then add the implementation of ConfigureAuth in Startup.Auth.cs in the App_Start folder. You’ll need to remove “.App_Start” from the namespace in the created file so it matches the namespace in Startup.cs: 

CODE
namespace <YourProjectName> 

{ 

    public partial class Startup 

    { 

        public static string OidcAuthority = ConfigurationManager.AppSettings["oidc:Authority"]; 

        public static string OidcRedirectUrl = ConfigurationManager.AppSettings["oidc:RedirectUrl"]; 

        public static string OidcClientId = ConfigurationManager.AppSettings["oidc:ClientId"]; 

        public static string OidcClientSecret = ConfigurationManager.AppSettings["oidc:ClientSecret"]; 

 

        public void ConfigureAuth(IAppBuilder app) 

        { 

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); 

            app.UseCookieAuthentication(new CookieAuthenticationOptions()); 

 

            var oidcOptions = new OpenIdConnectAuthenticationOptions 

            { 

                Authority = OidcAuthority, 

                ClientId = OidcClientId, 

                ClientSecret = OidcClientSecret, 

                PostLogoutRedirectUri = OidcRedirectUrl, 

                RedeemCode = true, 

                RedirectUri = OidcRedirectUrl, 

                ResponseType = OpenIdConnectResponseType.Code, 

                Scope = OpenIdConnectScope.OpenId 

            }; 

 

            app.UseOpenIdConnectAuthentication(oidcOptions); 

        } 

    } 

} 
 

Configure global security settings 

Add a setting to Application_Start in Global.asax.cs for the anti-forgery token. 

If you are using an (unsupported) .NET runtime version less than 4.6.2, you'll also need to add the ServicePointManager line to enable TLS 1.2: 

CODE
void Application_Start() 

{ 

... 

    AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; 

    // Not needed for .NET 4.6.2 and later: TLS 1.2 is already enabled: 

    ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; 

} 
 

Add login and logout 

Add a new partial page in the Views\Shared folder named _LoginPartial.cshtml. This will display a Login link if a user session does not exist and will display links to logout and view the returned claims if a user session does: 

CODE
@if (Request.IsAuthenticated) 

{ 

    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" })) 

    { 

        @Html.AntiForgeryToken() 

        <ul class=" navbar-nav navbar-right"> 

            <li>@Html.ActionLink("Claims", "Claims", "Account", routeValues: null, htmlAttributes: new { title = "Claims", @class = "nav-link" })</li> 

            <li class="nav-item"><a class="nav-link" href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li> 

        </ul> 

    } 

} 

else 

{ 

    <ul class="navbar-nav navbar-right"> 

        <li>@Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink", @class = "nav-link" })</li> 

    </ul> 

} 

 

Reference this in _Layout.cshtml after the main nav bar closing </ul>: 

CODE
... 

</ul> 

@Html.Partial("_LoginPartial") 
 

Create claims page 

Create a new folder in Views called Account. Add a new partial view called Claims and paste in the following code which will display all claims that have been returned: 

CODE
@using System.Security.Claims 

@{ 

    ViewBag.Title = "Claims"; 

} 

<h2>@ViewBag.Title</h2> 

<h4>Claims Present in the Claims Identity:</h4> 

<table class="table table-striped table-bordered table-hover table-condensed claim-table"> 

    <tr> 

        <th class="claim-type claim-data claim-head">Claim Type</th> 

        <th class="claim-data claim-head">Claim Value Type</th> 

        <th class="claim-data claim-head">Claim Value</th> 

    </tr> 

    @foreach (Claim claim in ClaimsPrincipal.Current.Claims) 

    { 

        <tr> 

            <td class="claim-type claim-data">@claim.Type</td> 

            <td class="claim-data">@claim.ValueType</td> 

            <td class="claim-data">@claim.Value</td> 

        </tr> 

    } 

</table> 
 

Create AccountController.cs in the Controllers folder: 

CODE
namespace <YourProjectName>.Controllers 

{ 

    public class AccountController : Controller 

    { 

        public void Login(string returnUrl = "/") 

        { 

            if (!Request.IsAuthenticated) 

            { 

                HttpContext.GetOwinContext().Authentication.Challenge(); 

                return; 

            } 

            Response.Redirect("/"); 

        } 

 

        public void LogOff() 

        { 

            if (Request.IsAuthenticated) 

            { 

                var authTypes = HttpContext.GetOwinContext().Authentication.GetAuthenticationTypes(); 

                HttpContext.GetOwinContext().Authentication.SignOut(authTypes.Select(t => t.AuthenticationType).ToArray()); 

            } 

            Response.Redirect("/"); 

        } 

 

        [Authorize] 

        public ActionResult Claims() 

        { 

            return View(); 

        } 

    } 

} 
 

Run the site, and check that Login takes you to OpenAthens, where you can log in with a personal account (create one in the Admin area https://admin.openathens.net/ if you haven't already done so). Check you can view the claims after logging in. At this stage, you only have a limited set of claims, which are included in the initial response OIDC response. These claims don’t tell you enough about the user to know whether to grant them access. To do this, you need to fetch additional claims from the OpenID Connect userinfo endpoint. 

Fetch additional claims 

The Microsoft .NET Framework implementation of OpenID Connect does not fully implement the OpenID Connect protocol. Specifically, it does not support fetching additional user claims from the userinfo endpoint. If you were using .NET Core, you would just need to set GetClaimsFromUserInfoEndpoint = true in the config, but the .NET Framework implementation doesn’t support this. We need to write our own method to retrieve the additional claims. Add a new method in the Startup.Auth.cs: 

CODE
private async Task GetUserInfoAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification) 

{ 

    var accessToken = notification.ProtocolMessage.AccessToken; 

    var backchannel = notification.Options.Backchannel; 

    var identity = notification.AuthenticationTicket.Identity; 

    var metadataAddress = notification.Options.MetadataAddress; 

 

    var configuration = await OpenIdConnectConfigurationRetriever.GetAsync(metadataAddress, new CancellationToken()); 

    var request = new HttpRequestMessage(HttpMethod.Get, configuration.UserInfoEndpoint); 

    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); 

    var responseMessage = await backchannel.SendAsync(request); 

    responseMessage.EnsureSuccessStatusCode(); 

    var response = await responseMessage.Content.ReadAsStringAsync(); 

    var userInfo = JObject.Parse(response); 

    // Add claims we don't already have 

    foreach (var pair in userInfo) 

    { 

        var claimValue = userInfo.TryGetValue(pair.Key, out JToken value) ? value.ToString() : null; 

        if (!identity.HasClaim(pair.Key, claimValue)) 

        { 

            identity.AddClaim(new Claim(pair.Key, claimValue, ClaimValueTypes.String)); 

        } 

    } 

} 
 

Then wire this up by adding a Notifications property to the existing oidcOptions: 

CODE
var oidcOptions = new OpenIdConnectAuthenticationOptions 

{ 

    ..., 
    Notifications = new OpenIdConnectAuthenticationNotifications 

    { 

        SecurityTokenValidated = GetUserInfoAsync 

    } 

}; 
 

Re-run the application and access the claims page again. You should now see a much longer list of claims. 

The claims you will be most interested in are: 

  • urn:oid:1.3.6.1.4.1.5923.1.1.1.9, also known as scopedAffiliation. Use this to decide whether the allow the user into your application, based on your customer database. 

  • urn:oid:1.3.6.1.4.1.5923.1.1.1.10, also known as targetedID. This is a persistent, pseudonymous identifier for the user. Use this as the key for storing user personalisation. 

In the real world you would use the scopedAffiliation to decide whether to grant access to protected content. 

Be aware that claims can be multi-valued and may be returned as either a string or an array, depending on the number of values available for the user. For example, urn:oid:1.3.6.1.4.1.5923.1.1.1.9 could be returned in either of these forms: 

  • "member@<domain>" 

  • ["member@<domain>", "student@<domain>"] 

You will need to allow for this when parsing the claims. 

For more information about the attributes available from OpenAthens, and how to map them to OpenID Connect claims. See:

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.