Click here to Skip to main content
13,254,373 members (57,747 online)
Click here to Skip to main content
Add your own
alternative version

Stats

27.8K views
844 downloads
44 bookmarked
Posted 22 Apr 2017

Simple OAuth2 Authorization Server with Identity Server and .NET Core

, 7 May 2017
Rate this:
Please Sign up or sign in to vote.
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 made already a great job providing an amazing documentation and many clear & useful quickstart examples. Sometimes, however, it is a bit complicated to understand how did the author of the example get 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 also for other developers.

Background

To understand what is this article 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 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 easy 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>
<head>
<title>Authorization server. Welcome page.</title>
</head>
<body>
<h1>Authorization server. Welcome page.</h1> 
</body> 
</html>

Configure the Startup class

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

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.

"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 importants 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:

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 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" }
          }
      };
    }
  }

}

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:

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:

// 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:

// 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:

.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

app.UseIdentityServer();

At the end, the method should look like this:

// 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:

"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)

Share

About the Author

Livio Francescucci
Architect EF Education First
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.

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionUpgrade to dotnetcore 2.0 Pin
JasenF14-Sep-17 13:10
professionalJasenF14-Sep-17 13:10 
AnswerRe: Upgrade to dotnetcore 2.0 Pin
prageeth.madhu9-Nov-17 3:51
memberprageeth.madhu9-Nov-17 3:51 
AnswerRe: Upgrade to dotnetcore 2.0 Pin
Member 1351390610-Nov-17 0:37
memberMember 1351390610-Nov-17 0:37 
QuestionReading values from appsettings.json in Config.cs Pin
tejas pinge12-Sep-17 6:03
membertejas pinge12-Sep-17 6:03 
QuestionJust Awesome Pin
Member 1337057623-Aug-17 5:09
memberMember 1337057623-Aug-17 5:09 
QuestionGreat Article - simple and to the point Pin
sjb_strat3-Jul-17 11:51
membersjb_strat3-Jul-17 11:51 
QuestionQuick and Concise. Pin
The Super Coder29-Jun-17 20:18
memberThe Super Coder29-Jun-17 20:18 
QuestionVery Nice article Pin
tkmaliren30-May-17 0:03
membertkmaliren30-May-17 0:03 
AnswerRe: Very Nice article Pin
Livio Francescucci1-Jun-17 2:23
memberLivio Francescucci1-Jun-17 2:23 
GeneralMy vote of 5 Pin
kbdguy8-May-17 7:42
professionalkbdguy8-May-17 7:42 
GeneralRe: My vote of 5 Pin
Livio Francescucci10-May-17 0:05
memberLivio Francescucci10-May-17 0: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.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.171114.1 | Last Updated 7 May 2017
Article Copyright 2017 by Livio Francescucci
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid