Click here to Skip to main content
15,881,172 members
Articles / Web Development / HTML

Simple OAuth2 Authorization Server with Identity Server and .NET Core

Rate me:
Please Sign up or sign in to vote.
4.83/5 (25 votes)
7 May 2017CPOL7 min read 110.2K   3.4K   64   18
A guided walk-through to build a simple Authorization Server and enable a Client Credentials workflow using Identity Server and .NET Core

Introduction

This article is a short and easy walk-through that will explain how to build an OAuth2 Authorization Server using the Identity Server open source middleware and hosting it inside a .NET Core Web Server.

The authors of the Identity Server project already did a great job providing an amazing documentation and many clear & useful quickstart examples. Sometimes, however, it is a bit complicated to understand how the author of the example got there. And often, rebuilding the same example from scratch helps a lot to understand the technology that we are trying to learn.

This article comes from these considerations. The idea is to share with you my experience while learning this subject, hoping that it can be of some value for other developers as well.

Background

To understand what this article is about, you might want to learn more about:

Let's Create Our Authorization Server

In the next section, I'm going to explain the code (almost) step-by-step. And because I know that none of us likes to read too much, I organized every section with clear paragraph titles, so you can just scroll it all and find the part that might be more interesting for you. Let's go!

Create an Initial WebAPI Project in Visual Studio 2017

The entire example is currently just for VS2017, built using .NET Core 1.1. Here's how to start:

  • Open VS2017 and create a new project, choosing a VisualC# -> Web -> ASP.NET Core Web Application (.NET Core). Name it AuthorizationServer. Press OK.
  • Now choose the type WebApi project. Select ASP.NET Core 1.1. Press OK again. The project is created.

Add the NugetPackage IdentityServer4 to the WebAPI Server

  • Browse the Package Manager and install the package IdentityServer4. I installed the version 1.5.0, latest stable at the time I'm writing.

Add a Welcome Page to the WebAPI Server

To make your authorization server reachable with a browser, and for you to easily understand if the server is up and running, you can add a basic controller and a welcome page. I will detail the steps I followed, for those that are not familiar with the ASP.NET MVC framework:

Add a MVC Controller

  • In the folder Controllers, add a new controller named HomeController.
  • Adding the new controller, VS will ask what dependencies to add to the project. You can choose Minimal Dependencies for now.
  • At the time I'm writing, after adding the dependencies, I need to add the controller again. I think it is a little bug of the UI....
  • Now, adding the controller, VS asks which scaffold to use. Choose MVC Controller - Empty.
  • Finally, choose the name HomeController. A basic controller that can manage an Index.cshtml page is now created.

Add a MVC View

  • Again, in VS2017, right click on the project and select Add new folder.
  • Name it Views (don't change it! It's a default option for ASP.NET MVC!).
  • Now, inside this Views folder, add another folder named Home.
  • Right click now on the folder Home and select Add -> New item -> MVC View Page (ASP.NET Core).
  • By default, the name of the view is Index.cshtml which is what we want. Press the button Add to add the new view.

Write the HTML Content of the View

Change the code in the Index.cshtml with the code below, just to create a welcome message:

HTML
<html>
<head>
<title>Authorization server. Welcome page.</title>
</head>
<body>
<h1>Authorization server. Welcome page.</h1> 
</body> 
</html>

Configure the Startup Class

Setup the proper route in the Startup.cs to be able to browse the website:

C#
public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

Now browse the file in Properties -> LaunchSettings.json and remove the line containing the property launchUrl from every section visible in the file.

JavaScript
"profiles": {
 "IIS Express": {
  "commandName": "IISExpress",
  "launchBrowser": true,
  "launchUrl": "api/values", //REMOVE THIS ONE
  "environmentVariables": {
   "ASPNETCORE_ENVIRONMENT": "Development"
  }
 },
 "AuthorizationServer": {
  "commandName": "Project",
  "launchBrowser": true,
  "launchUrl": "api/values", //REMOVE THIS ONE
  "environmentVariables": {
   "ASPNETCORE_ENVIRONMENT": "Development"
  },
  "applicationUrl": "http://localhost:50011"
 }

Without this property, Visual Studio will launch the home page following the default path, and will point automatically to the web page that we just created.

Launch the Project to Check That It Works...

...and to enjoy the achievement of a first step! Warning: some people reported some issues in VS2017 related to launching a WebAPI project using IISExpress. In case you are one of those, you can change the profile next to the button "Start project" switching from IISExpress to AuthorizationServer.

Configure Sample Scopes and Clients for the Client Credential Workflow

Now we can configure the most important elements of our Authorization Server: clientIds, clientSecrets, scopes.
These three elements are some of the basics for the Client Credential workflow. Don't forget to refer to the OAuth2 Client Registration documentation for more information!

These properties will define WHO can request an access token to your Authorization Server and WHAT can be done with that token.

To activate our initial sample configuration, just create a Config.cs class in our project, that looks like this:

C#
using IdentityServer4.Models;
using System.Collections.Generic;

namespace AuthorizationServer
{
  public class Config
  {
    // scopes define the API resources in your system
    public static IEnumerable<ApiResource> GetApiResources()
    {
      return new List<ApiResource>
      {
        new ApiResource("scope.readaccess", "Example API"),
        new ApiResource("scope.fullaccess", "Example API"),
        new ApiResource("YouCanActuallyDefineTheScopesAsYouLike", "Example API")
      };
    }

    // client wants to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
      return new List<Client>
      {
    new Client
          {
            ClientId = "ClientIdThatCanOnlyRead",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret1".Sha256())
            },
            AllowedScopes = { "scope.readaccess" }
          },
    new Client
          {
            ClientId = "ClientIdWithFullAccess",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret2".Sha256())
            },
            AllowedScopes = { "scope.fullaccess" }
          }
      };
    }
  }
}

Spend a Moment to Understand this Class...

Take a moment to understand how the configuration work in this class. This is the key part of our example.

We are firstly defining three scopes here:

C#
return new List<ApiResource>
      {
        new ApiResource("scope.readaccess", "Example API"),
        new ApiResource("scope.fullaccess", "Example API"),
        new ApiResource("YouCanActuallyDefineTheScopesAsYouLike", "Example API")
      };

As you can see, we can use as a scope any string we like. It's just an identifier, nothing else.

When requested, the AuthorizationServer will issue a JWT Token to a client, and based on the clientId, will include the proper scope in the token. Since the scope is encrypted in the token, there is no risk that the client that receives the token can change the scope and enable for himself more rights that we want.

The logic to actually use this scope will be in the Web API Server that we will create later (I'm planning to do it soon in another example / article) and will protect using this authorization server. The Web API Server, before DOING real stuff will check that the scope passed from the client contains the right authorization. This is the point where we are leveraging our Authorization Server.

Which ClientId can request a token, and which scope does it get? This is what is defined in the second part of the configuration class:

C#
// client want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
      return new List<Client>
      {
    new Client
          {
            ClientId = "ClientIdThatCanOnlyRead",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret1".Sha256())
            },
            AllowedScopes = { "scope.readaccess" }
          },
    new Client
          {
            ClientId = "ClientIdWithFullAccess",
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            ClientSecrets =
            {
              new Secret("secret2".Sha256())
            },
            AllowedScopes = { "scope.fullaccess" }
          }
      };
    }

Enable the IdentityServer Middleware Features

It's time to enable the IdentityServer features and complete the transformation of our empty web site in a real Authorization Server, giving it the possibility to manage and authenticate the clients that we configured in our Config class above.

To do so, we need just to include a couple of calls to the IdentityServer objects inside our Startup class.

Copy and paste the following method in the Startup class, replacing the old one:

C#
// This method gets called by the runtime. Use this method to add services to the container.     
public void ConfigureServices(IServiceCollection services)
    {
      // configure identity server with in-memory stores, keys, clients and scopes
      services.AddIdentityServer()
        .AddTemporarySigningCredential()
        .AddInMemoryApiResources(Config.GetApiResources())
        .AddInMemoryClients(Config.GetClients());

      //add framework services
      services.AddMvc();
    }

The method is "enabling" the IdentityServer middleware and adding an InMemory management for our scopes and clients.

This is the key point where we are now using the Config class created before:

it
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());

There are many, many more other options that can be configured on identityserver but are out of scope of this article. Here, we are just creating a quick start. Once again, check out the documentation, the guys there really developed an amazing open source library.

Now, in the method Startup.Configure, add the line:

C#
app.UseIdentityServer();

At the end, the method should look like this:

C#
// This method gets called by the runtime. Use this method to configure the
// HTTP request pipeline.
   public void Configure(IApplicationBuilder app, IHostingEnvironment env,
                         ILoggerFactory loggerFactory)
   {
     loggerFactory.AddConsole(Configuration.GetSection("Logging"));
     loggerFactory.AddDebug();

     app.UseIdentityServer(); //HERE WE ENABLE IDENTITY SERVER

     app.UseMvc(routes =>
     {
       routes.MapRoute(
         name: "default",
         template: "{controller=Home}/{action=Index}/{id?}");
     });
   }

Check Out that Everything Works

We are almost at the end. Our Authorization Server is ready to start!

So, again from VS2017, launch the project. The welcome page shows up to reassure you that the web service is up and running.
Now go to the following address (replace the port number with the port number of your server):

  • http://localhost:50151/.well-known/openid-configuration

If everything is working, a JSON file is loaded and shows up in the browser (or you are asked to download it, it depends on your browser). This is the JSON file with all the Configuration information generated from the Identity Server middleware.

Take a look to this JSON file. There is an important part in it, showing that the middleware has correctly understood your configuration. Is the part declaring the scopes that your Authorization Server supports, exactly the ones that you declared in the Config.cs:

JavaScript
"scopes_supported": [
   "scope.readaccess",
   "scope.fullaccess",
   "YouCanActuallyDefineTheScopesAsYouLike",
   "offline_access"
 ],

That's it! ...Well, For Now....

Your first Authorization Server is ready to be used. But, wait...how? A single Authorization Server on its own doesn't help too much if doesn't interact with an API to protect, or with a client to authorize. It needs some friends!

Therefore, in another article, we are going to learn how to protect a Web API server, accepting tokens issued from this Authorization Server. We will learn also how to create a client that can request a token and use it.

Meanwhile, I hope this example can help other people that, like me, would like to start playing a bit with some OAuth2 workflows and the powerful IdentityServer middleware. Share your feedback and comments!

History

  • 2017-04-22: First version
  • 2017-04-24: Added a link to download the source code
  • 2017-05-07: Fixed a step mentioning a wrong button name

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Switzerland Switzerland
I'm a software engineer, passionate about software development since I was 8yrs old.
I (almost) always worked with Microsoft technologies: from C++, VB and classic ASP, in the early 2thousands I moved to .NET Technologies...
My best expertise is about C# development on Web & Windows platforms, software architectures and design / testing patterns.

Comments and Discussions

 
GeneralUsing Owin middleware Pin
Member 124289832-Jan-19 2:24
Member 124289832-Jan-19 2:24 
Suggestionuse AddDeveloperSigningCredential instead of temporary Pin
dcarl6617-Dec-18 6:24
dcarl6617-Dec-18 6:24 
QuestionCan u add a step by step procedure for generating swagger against oauth2 client credentials configured in asp.net core 2.1 web api? Pin
Member 1397622912-Sep-18 2:36
Member 1397622912-Sep-18 2:36 
PraiseWorks perfectly Pin
Jon_Bailey5-Sep-18 6:40
professionalJon_Bailey5-Sep-18 6:40 
GeneralWon't load. Pin
Martin Cooke17-Apr-18 0:24
Martin Cooke17-Apr-18 0:24 
PraiseThanks Pin
cicatrizado3-Apr-18 8:06
cicatrizado3-Apr-18 8:06 
QuestionRSA key does not have a private key Pin
m199215-Dec-17 2:58
m199215-Dec-17 2:58 
Hi,

thanks for this work.

I have a question:
When I run your project, I'm able to run the server properly only once. The second or any next time when I type "dotnet run" in console, i get an error: System.InvalidOperationException: RSA key does not have a private key.
Then I need to delete tempkey.rsa file manually before I can run application for another time.

I modified this code a little bit, upgrading it to .NET Core 2.0 - there is "AddDeveloperSigningCredential()" method instead of: "AddTemporarySigningCredential()", and of course standard changes in Startup() and Main() methods.

Anyone has an issue?

Thanks.
QuestionUpgrade to dotnetcore 2.0 Pin
JasenF14-Sep-17 12:10
professionalJasenF14-Sep-17 12:10 
AnswerRe: Upgrade to dotnetcore 2.0 Pin
prageeth.madhu9-Nov-17 2:51
prageeth.madhu9-Nov-17 2:51 
AnswerRe: Upgrade to dotnetcore 2.0 Pin
RobertPaulH9-Nov-17 23:37
RobertPaulH9-Nov-17 23:37 
QuestionReading values from appsettings.json in Config.cs Pin
tejas pinge12-Sep-17 5:03
tejas pinge12-Sep-17 5:03 
QuestionJust Awesome Pin
Member 1337057623-Aug-17 4:09
Member 1337057623-Aug-17 4:09 
QuestionGreat Article - simple and to the point Pin
sjb_strat3-Jul-17 10:51
sjb_strat3-Jul-17 10:51 
QuestionQuick and Concise. Pin
Zeshan Munir29-Jun-17 19:18
Zeshan Munir29-Jun-17 19:18 
QuestionVery Nice article Pin
tkmaliren29-May-17 23:03
tkmaliren29-May-17 23:03 
AnswerRe: Very Nice article Pin
Livio Francescucci1-Jun-17 1:23
Livio Francescucci1-Jun-17 1:23 
GeneralMy vote of 5 Pin
kbdguy8-May-17 6:42
professionalkbdguy8-May-17 6:42 
GeneralRe: My vote of 5 Pin
Livio Francescucci9-May-17 23:05
Livio Francescucci9-May-17 23:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.