Getting SharePoint apps to work with ASP.NET Core was a bit of an interesting challenge, which I will share with you in this post. A nice new addition to the Office Dev PnP repository surfaced up from getting this working – a library/implementation for you to reuse in your day-to-day ASP.NET Core apps/add-ins development (see https://github.com/OfficeDev/PnP/tree/master/Solutions/AspNetCore.Authentication). You add this to your ASP.NET Core projects and you can authenticate as a SharePoint provider-hosted app. Hopefully, we will integrate this into the PnP Core namespace & package as the time comes.
The Problem
In order to build ASP.NET MVC 5 apps (not ASP.NET Core) we have the option to use the Office Developer Tools for Visual Studio 201X, which gives us developers an “App for SharePoint” project template (notice the App terminology never changed to Add-ins!). We create one of these and get an ASP.NET MVC 5 web application project together with an App project with the manifest and packaging capabilities. We get files such as TokenHelper.cs, SharePointContext.cs and SharePointContextFilterAttribute.cs with all the plumbing for token requesting, storage, cookie generation and authorization. Good stuff, but now old.
We currently don’t have an equivalent for ASP.NET Core. There is no toolset in Visual Studio to develop apps/add-ins that run on ASP.NET Core, no project template. I have asked Microsoft around and as far as I got responses there is no team assigned to develop anything. The guy who developed the toolset moved on to another team. What’s even more complicated is the fact that ASP.NET Core is drastically different, so we can’t just do a copy/paste port and recompile it for the DNX runtime. We have to rewrite the whole implementation.
This blog post is my story on the rewriting of it and bringing the SharePoint developer community a way to do SharePoint apps with ASP.NET Core.
Business Justification
Why would you use ASP.NET Core for SharePoint Apps?
- You may want to migrate existing ASP.NET MVC 5 Provider-Hosted Add-ins to ASP.NET Core to make use of the benefits that the new platform offers
- Introduce an ASP.NET Core / SharePoint Add-in story, because currently there is none. PnP to the rescue!
- You might not be ready for Azure AD authentication and the Active Directory Authentication Library (or you just don’t want to use it for whatever reason, such as the new MSAL package)
- You want your provider-hosted Office Marketplace app to use ASP.NET Core.
The Solution – PnP to the Rescue!
The actual solution to the above problem is quite simple:
- create a library that performs the necessary authentication handshaking and authorization to be able to run SharePoint apps/add-ins on ASP.NET Core.
- Bundle it as part of PnP so it gets adoption, attention, contribution and support
- Publish it as a NuGet package so everyone could add it and use it in their ASP.NET Core web applications.
Some terminology to clarify things:
- AspNetCore.Authentication – this is the name of the Office PnP Solution in the repository. It contains a sample web application, a SharePoint app project and the library below
- OfficeDevPnP.Core.Framework.Authentication – the name of the library (and namespace) with all of the authentication logic & plumbing. This is what will be pushed out as a NuGet package so it can easily be added to any ASP.NET Core project.
Design Goals
I’ve used the following principles and goals in the design of this solution:
- It must be very easy to “Add” and “Use”, based on the plug ‘n’ play model of ASP.NET Core (Add and Use are special Core terms)
- Enforce the least amount of change to the developer experience, SharePoint developers should know how to use this straight away
- Base it on the ASP.NET Core middleware design
- Figure out how to compile to DNX Core. This is currently not achieved. We must compile to .NET Framework rather than Core due to a dependency on Microsoft.IdentityModel.Extensions.dll
- Implement with ASP.NET Core configuration and logging patterns/classes
- Base it on the ASP.NET Authorization & Authentication model so the request is truly authenticated and authorized from the System.Security.Claims.IIdentity perspective.
- Be able to use the [Authorize] attribute to lock down controller methods
- Figure out how to include it as part of the OfficeDevPnP.Core library. This is currently not achieved. We cannot add it to the true Core assembly due to the old project format and the fact that we’re using the/ DNX assembly format.
- Release it through NuGet so anyone could add it to their ASP.NET Core web application.
How does SharePoint Authentication/Authorization work in the ASP.NET MVC 5 implementation?
When we are tackling a port/upgrade/rewrite of something we must really understand how it works to build on it, improve it and get results. This is a quick refresher of how SharePoint provider-hosted authentication is implemented in ASP.NET MVC 5.
- We had tooling, the Office Developer Tools for VS, which gave us a starter template project with several class files that made authentication work with SharePoint.
- From that point onwards we literally press F5 and VS does what it needs to do to get a “Hello World” solution running. It worked.
- The TokenHelper.cs & SharePointContext.cs set of classes handled the required plumbing & business logic.
- These classes had a dependency on Microsoft.IdentityModel.Extensions.dll for S2S and OAuth2 token/claims manipulation.
- The class design and implementation followed the MVC 5 patterns and development model
- We had a publish & deploy experience, quite satisfactory for most.
- A SharePointContextFilter attribute handles redirects and retrieves a SharePointContext object in session.
- The SPCacheKey is stored in a cookie to identify users between their redirects and web requests
- TokenHelper deals with authorization codes and access tokens, together with Microsoft.IdentityModel.Extensions.dll
- The SharePointContext.cs file contains a total of 7 classes, some of them abstract. SharePointAcsContext/Provider and SharePointHighTrustContext/Provider are important as they hold the specific implementations of authentication through ACS and through the High-Trust model (using symmetric keys from certificates).
- When the user clicks on an installed app url, or the app icon on the site contents page in SharePoint, the user is taken to AppRedirect.aspx. This page generates an HTML form element posting to the URL of the app, providing it with a context token and other useful information so the provider-hosted app can authenticate the request. I will not go into greater detail of this process, but it is crucial to understand it. It is well documented by fellow bloggers.
- There is no true implementation of ASP.NET authorization (the [Authorize] attribute) at the MVC controller level. Poo.
NOTE: The above are key points that describe how the AuthZ mechanism works when we talk about SharePoint Provider-hosted apps. Note that it is truly authorization, not authentication. SharePoint handles authentication through its identity provider, then OAuth is used to share resources between SharePoint and the application you are building. OAuth 2 is an authorization protocol (see https://tools.ietf.org/html/rfc6749). On this point, read further down for some wording misnomers…
ASP.NET Core Implementation Deep Dive
OK so here we go. So far it is called AspNetCore.Authentication, which is probably the name that will last for a while until Vesa makes me change it. In the following section I list the key implementation decisions made so far. Here is a quick screenshot of the classes created to meet our needs:
Middleware
It is based on the ASP.NET middleware concepts, registered through extension methods on the IApplicationBuilder object during the Configure method in Startup.cs
//Add SharePoint authentication capabilities
app.UseSharePointAuthentication(
new SharePointAuthenticationOptions()
{
CookieAuthenticationScheme = “AspNet.ApplicationCookie”,
//I really don’t like how config settings are retrieved, but that is how the ASP.NET guys do it in their samples
ClientId = Configuration[“SharePointAuthentication:ClientId”],
ClientSecret = Configuration[“SharePointAuthentication:ClientSecret”],
//Handle events thrown by the auth handler
Events = new SharePointAuthenticationEvents()
{
OnAuthenticationSucceeded = succeededContext =>
{
return Task.FromResult<object>(null);
},
OnAuthenticationFailed = failedContext =>
{
return Task.FromResult<object>(null);
}
}
}
);
Authentication & Authorization
The AspNetCore.Authentication library is based on the Authentication & Authorization libraries part of ASP.NET Core (see https://github.com/aspnet/Security/tree/dev/src/Microsoft.AspNetCore.Authentication and https://github.com/aspnet/Security/tree/dev/src/Microsoft.AspNetCore.Authorization ). This is important to achieve one of the design goals – consider the web request authenticated and be able to use the [Authorize] attribute and relevant AuthZ capabilities.
The provided SharePointAuthenticationMiddleware class inherits the framework’s AuthenticationMiddleware class
public class SharePointAuthenticationMiddleware : AuthenticationMiddleware<SharePointAuthenticationOptions> { …removed… }
The CreateHandler method returns an instance of the SharePointAuthenticationHandler, holding all of the logic for authentication handler:
protected override AuthenticationHandler<SharePointAuthenticationOptions> CreateHandler()
{
return new SharePointAuthenticationHandler();
}
NOTE: Back to the point on AuthZ vs AuthN, the ASP.NET classes use the Authentication term, even though there is a case where a remote party does the authentication. This might be a topic for argument for the purists and scientists out there.
Cookies and Session
The cookie handling implementation has been entirely rewritten in ASP.NET Core, so naturally that had a knock-on effect on the SharePointContext classes in AspNetCore.Authentication that manage the cookie to track the user’s requests.
//The following code generates a cookie in the response with the SPCacheKey as a value
var options = new CookieOptions() { HttpOnly = true, Secure = true };
httpContext.Response.Cookies.Append(SPCacheKeyKey, spAcsContext.CacheKey, options);
//read the cookie value
HttpCookie spCacheKeyCookie = new HttpCookie(SPCacheKeyKey, httpContext.Request.Cookies[SPCacheKeyKey]);
string spCacheKey = spCacheKeyCookie != null ? spCacheKeyCookie.Value : null;
Luckily, the code is pretty simple J
Handling Events
In a typical use-case scenario, you would reference the compiled library and retrieve it through the NuGet package manager. That means that you can’t really plug in your own code, so I’ve implemented an events model. The idea is that you as a developer will subscribe to the events and deal with custom logic in the handlers. The following are the currently implemented events:
/// <summary>
/// Invoked when the SharePoint authentication process has succeeded and authenticated the user.
/// </summary>
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Invoked when the authentication handshaking failed and the user is not authenticated.
/// </summary>
public Func<AuthenticationSucceededContext, Task> OnAuthenticationSucceeded { get; set; } = context => Task.FromResult(0);
See the Events = new SharePointAuthenticationEvents() { } line in the UseSharePointAuthentication extension method.
Let me know if you need any other events handled.
Getting Started
So, to get this up and running, do the following:
- Clone and build the OfficeDevPnP.Core.Framework.Authentication project and add a reference to the output NuGet package to your ASP.NET Core application.
- Add the following to the Startup.cs Configure method in your ASP.NET Core web application:
app.UseSharePointAuthentication(
new SharePointAuthenticationOptions()
{
CookieAuthenticationScheme = “AspNet.ApplicationCookie”,
ClientId = Configuration[“SharePointAuthentication:ClientId”],
ClientSecret = Configuration[“SharePointAuthentication:ClientSecret”],
}
);
- The library needs Session and Cookies in order to keep track of the client requests during redirects. Add the following to the Configure method:
app.UseSession();
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AutomaticAuthenticate = true,
CookieHttpOnly = false,
AutomaticChallenge = false,
AuthenticationScheme = “AspNet.ApplicationCookie”,
ExpireTimeSpan = System.TimeSpan.FromDays(14),
LoginPath = “/account/login”
}
);
Note that the AuthenticationScheme must be the same in both Use instructions. This is so the SharePointAuthenticationHandler also signs in the cookie authentication middleware to issue the needed cookie.
- For the Session & Cookie pipeline additions to work, the following needs to be added to the ConfigureServices method of Startup.cs:
services.AddCaching();
services.AddSession(o => { o.IdleTimeout = TimeSpan.FromSeconds(3600); });
-
There’s a few other intricacies in the whole setup:
- HTTPS – the web server must be serving over HTTPS. See the sample project for an example of setting up Kestrel with SSL
- Bower and NPM – if you are looking at the AspNetCore.Mvc.StarterWeb project you might need to restore all packages to get it running.
That should do it!
Frequently Asked Questions
What about UseRemoteAuthentication?
The ASP.NET Core authentication library/package (Microsoft.AspNetCore.Authentication) has a sample implementation of “Remote” or “External” authentication, packaged in a RemoteAuthenticationHandler class. I spent a good amount of time researching those at the time of writing this library, however I came to the conclusion that it is way too soon to use those classes. It was heavily changed/updated and not stable/ready enough to use when I needed it.
While it sounds like the perfect way to achieve what we want, for the time being I’ve kept away from it. I will keep a very close eye and rewrite the implementation if required, when we have something stable to work with.
What about Microsoft.IdentityModel.Extensions.dll?
This one’s a bit of a bitch. I’ve posted a question to the relevant Microsoft team to understand what the next plans for this library are and got this for an answer:
“We have moved forward on S2S specific libraries, but it is too early to share publically” (see https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/343 )
Not really satisfactory,but at least it is something, so I made the decision to just reference it for the time being until there’s new things coming.
The negative effect on the OfficeDevPnP.Core.Framework.Authentication library is that we must compile to the .NET Framework rather than .NET Core since it is compiled for .NET 4.5.2, but that is not such a huge issue because the CSOM library is in the same boat. Until both implementations are ready for .NET Core we will have to live with the fact that we’re compiling for the full framework. I’ll be working on communicating with the right people on the needs and challenges here.
I’m also looking at the option to completely rewrite the GetAccessToken() methods in TokenHelper.cs, replacing all instances of OAuth2S2SClient and similar classes with custom ones, or reuse some if Microsoft do something in this space. This will allow us to ditch this damn library.
What about compiling for .NET Core?
We’re gonna have to recompile the Microsoft.SharePoint.CSOM.dll library for this. It’s too big of a task for me (having to run OneBit Software in my work time), but maybe with the PnP folks we can lobby Microsoft to do it, or just recruit more lads and las to help us out. Currently, it is not on the immediate priority list, but I would love to do this one day.
Will this have Visual Studio tooling?
At this stage there are no plans for this, given that the actual app manifest project is still completely operable and you can use it as you always have. If you check out the ASP.NET Core model, things are a bit different now. You add your minimum amount of NuGet packages that you need through the dependency section in the project configuration file and the package manager interfaces, and you only get what you need. ASP.NET Core is minimalist and cool.
In that sense, my goal is to host OfficeDevPnP.Core.Framework.Authentication as a public NuGet package and allow you to directly add it as a package, when you need it. Just like the ASP.NET Core model.
What about a pure client-side implementation?
No. Not here. The SharePoint provider-hosted AppRedirect.aspx mechanism is based on the OAuth 2 Authorization Code flow, which is not safe if the required tokens are transferred to the client. (For more details see my slides from the Azure AD workshop that I delivered in Lisbon).
A client-side authentication implementation must use the OAuth2 Implicit Grant flow, with is specifically designed for those use-cases. It is only as secure as the browser’s capability to secure Local Storage and cookie jars.
If you are looking for a pure JavaScript client-side implementation, see the ADAL.JS library and its implementation for AngularJS, but make sure you are aware of its flaws with IE and trusted zones.
Why do you have Gulp, Bower and NPM in the sample StarterWeb project?
The design goal of the sample web application was to use the ASP.NET Core Web Application project template and only add what is needed to demonstrate SharePoint provider-hosted authentication. It comes with all those goodies, so I prefer people to see the difference between a new project and the AspNetCore.Mvc.StarterWeb sample project, and decided not to remove them. People should get to know that stuff.
Future Plans and Upcoming Tasks
Here is a quick summary of what changes are being planned and worked on:
- Get it working with RTM, I’m waiting for updates on the ASP.NET authZ/authN libraries to figure out if any real architectural changes are required
- Decide on the High Trust story – we (PnP) still don’t know if it is important, so bug us if it is!
- Decide on the overall PnP strategy for ASP.NET Core, because this is the first solution/sample/component that is built on ASP.NET Core
- Decide if we need to get some VS tooling to work with this library
Thank you for reading this far (if you actually did!) and thanks to all of my Stockholm SharePoint friends for giving me the motivation to write this blog post on my flight back from SPS Stockholm. Tell Vesa that this stuff is important so we lift it up in the priority list J
A HUGE thanks to Velin Georgiev from OneBit Software for helping me code this up.