The standard library for OpenID Connect published by the Microsoft for .NET Framework 4 is not compatible with OpenAthens Keystone. OpenAthens has published an updated library that makes it possible to connect to OpenAthens Keystone in .NET 4.5 or later. Earlier versions are not supported.

It is assumed that user has knowledge of developing applications using ASP.NET in Visual Studio. This example is based on the ASP.NET WebForms Web Application template.

A completed sample web application created using these instructions is available at https://github.com/openathens/keystone-dotnet-45-webforms-sample.

Goal in this example

Authenticate a user and display all the received claims on a page. In the real world you would read the claims and feed them into your authorisation / user-session management process.

Instructions

Create project

Create a new project in Visual Studio, selecting the template “ASP.NET Web Application (.NET  Framework)” - select framework version 4.5 or higher. At the second stage, select the Web Forms template option. Don’t select any authentication options at this stage.

Configure the website to run on https by default:

  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.
  2. Right click the project and select properties, and in the Web tab, paste the https copied above into the Project Url field in place of the existing http URL.

Create a new Application record in the OpenAthens Service Provider dashboard, using the URL you copied in step 1 as the Application URL (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

Use NuGet package manager to add the following packages:

Microsoft.AspNet.Identity.Owin
Microsoft.Owin.Host.SystemWeb
OpenAthens.Owin.Security.OpenIdConnect
CODE

Configuration

Add the following appSettings to Web.config:

   <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}" />
CODE


For added security to avoid accidentally checking secure strings into source control, you could place these settings in a separate config file outside the project root and include a reference to, e.g.:

 <appSettings file="..\..\EnvironmentSettings.config">
   <add … />
 </appSetting>
CODE


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:

using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WebApplication1.Startup))]
namespace WebApplication1
{
   public partial class Startup
   {
       public void Configuration(IAppBuilder app)
       {
           ConfigureAuth(app);
       }
   }
}
CODE


and secondly, 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):

using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using OpenAthens.Owin.Security.OpenIdConnect;
using Owin;
using System.Configuration;

namespace WebApplication1
{
   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,
               GetClaimsFromUserInfoEndpoint = true,
               PostLogoutRedirectUri = OidcRedirectUrl,
               RedirectUri = OidcRedirectUrl,
               ResponseType = OpenIdConnectResponseType.Code,
               Scope = OpenIdConnectScope.OpenId
           };

           app.UseOpenIdConnectAuthentication(oidcOptions);
       }
   }
}
CODE

Configure global security settings

If your .NET runtime version is less than 4.6.1, you'll need to add a line to Application_Start in Global.asax.cs to enable TLS 1.2:

using System.IdentityModel.Claims;
using System.Web.Helpers;
...
        void Application_Start(object sender, EventArgs e)
        {
        ...
		    ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
        }
...
CODE

Add login and logout

Add login and logout links by adding the following code block immediately after the existing <ul>...</ul> navigation links in Site.Master:

                <asp:LoginView runat="server" ViewStateMode="Disabled">
                    <AnonymousTemplate>
                        <ul class="nav navbar-nav navbar-right">
                            <li>
                                <a runat="server"
                                    href="Site.Master"
                                    onserverclick="login_Click">Login</a>
                            </li>
                        </ul>
                    </AnonymousTemplate>
                    <LoggedInTemplate>
                        <ul class="nav navbar-nav navbar-right">
                            <li>
                                <asp:LoginStatus runat="server"
                                    LogoutAction="Redirect"
                                    LogoutText="Logout"
                                    LogoutPageUrl="~/"
                                    OnLoggingOut="Unnamed_LoggingOut" />
                            </li>
                        </ul>
                    </LoggedInTemplate>
                </asp:LoginView>
CODE


and the following code behind in Site.Master.cs:

using Microsoft.Owin.Security.Cookies;
using System;
...
        protected void login_Click(object sender, EventArgs e)
        {
            if (!Request.IsAuthenticated)
            {
                HttpContext.Current.GetOwinContext().Authentication.Challenge();
            }
        }

        protected void Unnamed_LoggingOut(object sender, EventArgs e)
        {
            if (Request.IsAuthenticated)
            {
                Context.GetOwinContext().Authentication.SignOut(CookieAuthenticationDefaults.AuthenticationType);
            }
        }
...
CODE


Login and logout should now be working. 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). Logout should work as well.

Add Claims page

The last step is to add a protected page that shows the OpenID Connect claims for the logged in user. Add a new page called Claims with the following content:

<%@ Page Title="Claims" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Claims.aspx.cs" Inherits="KeystoneDotNet45WebFormsSample.Claims" %>
<%@ Import Namespace="System.Security.Claims" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
    <h2>OpenID Connect Claims</h2>
    <dl>
        <asp:DataList runat="server" ID="dlClaims">
            <ItemTemplate>
                <dt><%# ((Claim) Container.DataItem).Type %></dt>
                <dd><%# ((Claim) Container.DataItem).Value %></dd>
            </ItemTemplate>
        </asp:DataList>
    </dl>
</asp:Content>
CODE


and the following in its code behind file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace KeystoneDotNet45WebFormsSample
{
    public partial class Claims : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Request.IsAuthenticated)
            {
                HttpContext.Current.GetOwinContext().Authentication.Challenge();
            }

            var claims = ClaimsPrincipal.Current.Claims;
            dlClaims.DataSource = claims;
            dlClaims.DataBind();
        }
    }
}
CODE


Add a link to the Claims page inside the LoggedInTemplate you added to Site.Master earlier:

                                <li>
                                    <a runat="server" href="~/Claims">View claims</a>
                                </li>
CODE


Now you can log in and view the claims.

Example of accessing specific claims

The claim you will be most interested in is eduPersonScopedAffiliation, which can be accessed as follows:

var personScopedAffiliation = (User.Identity as ClaimsIdentity)?.Claims
    .FirstOrDefault(c => c.Type == "eduPersonScopedAffiliation")?.Value;
CODE

In a real world application you would access this in a master page and use it to decide whether to grant access to protected content in pages based on that master.