Introduction to IdentityServer for ASP.NET Core (2019)

Building Authenticated Web app with C#, Blazor and ASP.NET Core 3.0

Building Authenticated Web app with C#, Blazor and ASP.NET Core 3.0

Goodbye Javascript! Now, i can Building an Authenticated Web App in C# with Blazor + ASP.NET Core 3.0.

Curious what the experience would be like to trade in Javascript for C# on the front end? You are about to find out!

Table of Contents

  • Build a Basic Website with ASP.NET Core 3.0 + Blazor
  • Add User Authentication your Blazor Web App
  • Set Up Your Okta Account to handle the ASP.NET Core 3.0 Blazor App
  • Configure Your Blazor App to use Okta as the External Auth Provider
  • Add User Login to your Blazor Web App UI
  • Test Okta Registration and Login for Your Blazor App
  • Learn More about ASP.NET Core, Blazor and Authentication

For many years, Javascript (and it’s child frameworks) have had their run of the DOM (Document Object Model) in a browser, and it took having that scripting knowledge to really manipulate client-side UI. About 2 years ago, all of that changed with the introduction of Web Assembly - which allows compiled languages to be interpreted client-side and is fully supported across all browsers now. Microsoft’s answer to this was the creation of Blazor. Allowing C# developers to build their entire stack in .NET, including UI, was an exciting proposition. For some time Blazor has been in preview but is now included as a general release on September 23rd, 2019 along with the next iteration of .NET Core - version 3.0.

In order to build with Blazor and ASP.NET Core 3.0, you need the following prerequisites installed and ready to go:

Build a Basic Website with ASP.NET Core 3.0 + Blazor

Now that you have your dev environment handy, let’s get familiar with what a basic website walkthrough would be like. There are two ways you can utilize this technology: client-side or server-side Blazor. For this example, the server-side option is the best choice for stability, as client-side Blazor is still new and working on the final release. Stay tuned for that implementation!

First, you’ll need to create a Blazor project. To get the latest Blazor project templates to work with Visual Studio or VS Code, simply install them from the command line/terminal from your base repo directory:

dotnet new -i Microsoft.AspNetCore.Blazor.Templates::3.0.0-preview9.19424.4

Visual Studio (16.3 or later) will detect that the templates have been installed and surface them to you without the need for any additional extensions. Now it’s time to scaffold your new project. From your parent directory of code repositories, execute the following command:

dotnet new blazorserver -o OktaBlazorAspNetCoreServerSide

Once it’s been run, open up the OktaBlazorAspNetCoreServerSide folder in Visual Studio Code. Once loaded, if you look in the bottom right-hand corner of the IDE you will see a permission request to add assets to the build. Select Yes.

Now that everything has been loaded up, return to your command line/terminal and run the project.

dotnet run

Launch your browser of choice and navigate to https://localhost:5001. You should see a templated website.

Add User Authentication your Blazor Web App

ASP.NET Core 3.0 has brought along with it some hefty changes to the libraries and dependencies from previous versions of .NET Core. To get started with using an external OAuth provider, like Okta, there is a NuGet package you need to add to the project. Fire up your command line/terminal window in VS Code and add the Okta .NET SDK:

dotnet add package Okta.Sdk --version 1.4.0

In 3.0, ASP.NET Core ships as part of the .NET Core shared framework. The metapackage that was used for 2.x apps is no longer used. The first line of your project file that references the Web SDK is what pulls in the shared assemblies for ASP.NET Core.

For user authentication with OAuth, there is an additional layer of information you will use, called Open ID Connect (OIDC). While much of handling authentication is baked into the new 3.0 framework, OIDC is not included, so you’ll need to add a quick reference to that.

dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect --version 3.0.0-preview9.19424.4

Authentication works by redirecting users to the Okta website, where they will log in with their credentials, and then be returned to your site via the URL you will configure in the next section. Add the following code to the very top of your appsettings.json file, inside of the first brackets, and separate it from the rest of the settings by adding a comma after it.

"Okta": {
    "Issuer": "https://okta.okta.com/oauth2/default",
    "ClientId": "{yourClientId}",
    "ClientSecret": "{yourClientSecret}"
  }

Your file should look like this:

Just to make sure everything still can run, go ahead and execute dotnet run again.

Set Up Your Okta Account to handle the ASP.NET Core 3.0 Blazor App

Execute the following steps to configure Okta so that users can register themselves for an account.

  1. From the Administrative Dashboard, hover over Users and click Registration
  2. Click Enable Registration
  3. Save the changes

Once you have access to your account you can proceed to the Dashboard using a link like the one below: https://okta.okta.com/admin/dashboard

On the Dashboard, click Applications in the main menu and on the Application screen, click Add Application. Then select Web and click Next.

On the Create New Application screen, set the following values:

  • Name: OktaBlazorAspNetCoreServerSide

  • Base URIs: https://localhost:5001/

  • Login redirect URIs: https://localhost:5001/authorization-code/callback

Click Done, then click Edit next to General Settings on your newly created Okta app. Edit the following values: Logout redirect URIs: https://localhost:5001/signout-callback-oidc Initiate login URI: https://localhost:5001/authorization-code/callback

Once you’ve saved those values, scroll down and take note of the ClientID and ClientSecret.

ClientId refers to the client ID of the Okta application ClientSecret refers to the client secret of the Okta application Issuer will need the text {yourOktaDomain} replaced with your Okta domain, found at the top-right of the Dashboard page.

You will use your Okta account settings to update those values in the appsettings.json file in your project.

Configure Your Blazor App to use Okta as the External Auth Provider

Great! Now that Okta has been configured and is ready to go, there are a few changes that need to be made to the application startup.

Add these using statements to your Startup.cs file:

using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.IdentityModel.Tokens;

Replace all the code in the ConfigureServices method with the code below.

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    services.AddAuthorizationCore();
    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.ClientId = Configuration["okta:ClientId"];
        options.ClientSecret = Configuration["okta:ClientSecret"];
        options.Authority = Configuration["okta:Issuer"];
        options.CallbackPath = "/authorization-code/callback";
        options.ResponseType = "code";
        options.SaveTokens = true;
        options.UseTokenLifetime = false;
        options.GetClaimsFromUserInfoEndpoint = true;
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        };
    });
}

ASP.NET Core 3.0 has new options to configure the services in this file. UseAuthorization has been newly added to 3.0 project templates.

In the Configure() method of your Startup.cs file add this line just before the app.UseEndpoints() method:

app.UseAuthentication();
app.UseAuthorization();

In this example, you'll see there's a new UseEndpoints method in Startup.cs. This is what enables the new endpoint routing system in ASP.NET Core. All 3.0 project templates use that now. Think of this as a more performant routing system.

NOTE: In the context of Blazor apps, endpoint routing is the more holistic way of handling redirects. This is covered here in depth. Also, see this documentation to learn more about it. Endpoint routing shipped in ASP.NET Core 2.2, but it didn't become the default in the templates until 3.0.

Add User Login to your Blazor Web App UI

Time to add some user personalization to this app! Open Shared/MainLayout.razor and add the following HTML right before the About link.

<AuthorizeView>
<Authorized>
            <a href="Identity/Account/Manage">Hello, @context.User.Identity.Name!</a>
            <a href="Identity/Account/LogOut">Log out</a>
    </Authorized>
    <NotAuthorized>
            <a href="Identity/Account/Register">Register</a>
            <a href="Identity/Account/Login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

Using <AuthorizeView> is the easiest way to access authentication data, and is useful when you need to display a user’s name. The <AuthorizeView> component exposes a context variable of type AuthenticationState. In order to add this state to our app, open App.razor and wrap the HTML you see with <CascadingAuthenticationState> tags at the parent level. It should look like this:

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly">
        ...
    </Router>
</CascadingAuthenticationState>
Test Okta Registration and Login for Your Blazor App

That’s it! To test it out, go back to the command line/terminal and execute dotnet run.

Then navigate to http://localhost:5001 in your browser. Click Login and you should be redirected to the Okta Sign-In Page.

Because you configured your Okta org for self-registration, there should be an option at the bottom of the widget to allow users to register for a new account.

Now you have a website with a working login and user registration form. Your website also allows users to recover lost passwords. By repeating these steps you can create a network of tools that your users can access all with the same login.

All of that and not one line of Javascript. The future is looking bright for C#, give it a go with Blazor!

Thank for reading! Originally published on scotch.io

Building and Securing Web APIs with ASP.NET Core 3.0

Building and Securing Web APIs with ASP.NET Core 3.0

Learn how to build Web APIs with the new ASP.NET Core 3.0 and how to secure them with Auth0 authentication and authorization features.

Prerequisites

Before starting to build your Web API, you need to ensure you have installed the right tools on your machine. In particular, since you are going to use ASP.NET Core 3.0, you need to check if you have installed the .NET Core 3.0 SDK by typing the following command in a terminal window:

dotnet --version

You should get as a result the value 3.0.100 or above. If don't, you should download the .NET Core 3.0 SDK and install it on your machine.

If you are going to use Visual Studio, be aware that you need to use Visual Studio 2019 16.3 or Visual Studio for Mac 8.3 or above.

Note: If you update Visual Studio to the latest version, you will get .NET Core 3.0 SDK bundled.

Creating an ASP.NET Core Web API Project

You are now ready to build your Web API with ASP.NET Core 3.0. The Web API you are going to build will provide a few endpoints that allow you to manage a glossary of terms. So, you will be able to perform the typical CRUD (Create, Retrieve, Update, Delete) operations on the list of term definitions.

In order to create this project, type the following commands in a terminal window:

dotnet new webapi -o Glossary

This command creates a folder named Glossary and generates inside it the content for the ASP.NET Core project from the webapi template.

If you are using Visual Studio, you can create your project by selecting the API template as shown in the following picture:

Then follow the steps shown by the wizard to generate the project.

Note: In the rest of the article, the .NET Core CLI (Command-Line Interface) will be used to manage the project, but you can run the same steps by using Visual Studio as well.

Running the Sample Project

Once the project has been created, you should make sure everything is okay. So, in the terminal window, move to the Glossary folder and launch the sample application by typing the following commands:

cd Glossary
dotnet run

If this is the very first time you run an ASP.NET Core application, you should trust the HTTPS development certificate included in the .NET Core SDK. This task depends on your operating system. Please, take a look at the official documentation to apply the proper procedure.

If everything is okay, open a terminal window and type the following command:

curl --insecure https://localhost:5001/weatherforecast

You should get a bunch of data similar to the following:

[
  {
    "date": "2019-10-03T11:51:27.955275+02:00",
    "temperatureC": 9,
    "temperatureF": 48,
    "summary": "Mild"
  },
  {
    "date": "2019-10-04T11:51:27.95531+02:00",
    "temperatureC": -18,
    "temperatureF": 0,
    "summary": "Scorching"
  },
  {
    "date": "2019-10-05T11:51:27.955311+02:00",
    "temperatureC": -6,
    "temperatureF": 22,
    "summary": "Balmy"
  },
  {
    "date": "2019-10-06T11:51:27.955311+02:00",
    "temperatureC": 35,
    "temperatureF": 94,
    "summary": "Balmy"
  },
  {
    "date": "2019-10-07T11:51:27.955312+02:00",
    "temperatureC": -13,
    "temperatureF": 9,
    "summary": "Warm"
  }
]

This is the output of the sample Web API application that comes with the webapi project template.

Note: The flag --insecure you passed to the curl command disables the certificate validation that curl would make by default. This check would cause an error since the HTTPS development certificate provided by the .NET Core SDK is a self-signed certificate.

In the remainder of the article, you will use curl as the HTTP client to test the Web API. However, you can use the HTTP client you prefer, such as Postman or Insomnia.

To stop the Web API application, simply hit CTRL+C in the terminal window it is running into.

Anatomy of the Project

Regardless you created your project with the .NET Core CLI or Visual Studio, you will get the same collection of files and folders, like the ones shown in the following picture:

Taking a quick look at a few of these files gives you an idea of how a generic ASP.NET Core application works. In particular, the Program.cs file contains the starting point of the application. Its content looks like the following:

//Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Glossary
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

The static Main() method is the method that is automatically called when the application starts. It simply calls the CreateHostBuilder() method to create and configure a host for the application and eventually to run it. A host is an object that collects and provides services for the application. For example, in the ASP.NET context, it provides an HTTP server implementation, middleware components, configuration services, and so on.

As you can see, the webBuilder.UseStartup() method refers to the Startup type. This type is defined by the Startup class in the Startup.cs file. The default content of this file is shown below:

//Startup.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Glossary
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

The Startup class allows you to set up services for the current application. In particular, it allows you to register services via the ConfigureServices() method and to configure the HTTP request pipeline via the Configure() method. In the body of the latter method, you find a few statements that declare the middleware to use. Please, read the official documentation to get more information about the built-in middleware. Later on, you'll make some changes to the Configure() method in order to integrate Auth0 services.

The rest of the application code is located in the WeatherForecast.cs and WeatherForecastController.cs files. These files implement respectively the model and the Web APIs controller for the sample application coming with the .NET Core template. You are going to get rid of them and create your glossary application.

Creating a CRUD API

Now that you have ensured that the whole development environment is working properly and have taken a tour to understand the project's main elements, start to create your Web API.

As said before, your API will implement the CRUD operations on a glossary.

To get started, remove the WeatherForecast.cs file from the root of the project and the WeatherForecastController.cs file from the Controllers folder.

Creating the model

As a first step, create the model for the glossary Web API. It will be the representation of the resource that will be exposed to the clients. In particular, it will be implemented as a class representing the single glossary item. So, add a file named GlossaryItem.cs in the root folder of the project and put the following content in it:

//GlossaryItem.cs
namespace Glossary
{
    public class GlossaryItem
    {
        public string Term { get; set; }
        public string Definition { get; set; }
    }
}

This class simply defines a glossary item as a term and its definition.

Creating the controller

Now, you need to create the API controller to handle HTTP requests. Create the GlossaryController.cs in the Controllers folder and put the following code inside it:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        private static List<GlossaryItem> Glossary = new List<GlossaryItem> {
            new GlossaryItem
            {
                Term= "Access Token",
                Definition = "A credential that can be used by an application to access an API. It informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that has been granted."
            },
            new GlossaryItem
            {
                Term= "JWT",
                Definition = "An open, industry standard RFC 7519 method for representing claims securely between two parties. "
            },
            new GlossaryItem
            {
                Term= "OpenID",
                Definition = "An open standard for authentication that allows applications to verify users are who they say they are without needing to collect, store, and therefore become liable for a user’s login information."
            }
        };
    }
}

A controller in ASP.NET Core is a class inheriting from ControllerBase. The base class provides properties and methods that are useful for handling HTTP requests, as you will see later on.

At this stage, the GlossaryController class simply defines the static variable Glossary as a small list of glossary items. In a real-world scenario, the glossary items should be persisted into a database, but to keep things simple, your application will rely on this volatile solution.

The class is decorated with two attributes: ApiController and Route.

The ApiController attribute enables a few handy behaviors, like the automatic HTTP 400 responses when the model is in error, the automatic binding of URL parameters to method parameters, and similar.

The Route attribute allows you to map a URL pattern to the controller. In this specific case, you are mapping the api/[controller] URL pattern to the controller. At runtime, the [controller] placeholder is replaced by the controller class name without the Controller suffix. That means that the GlossaryController will be mapped to the api/glossary URL, and this will be the base URL for all the actions implemented by the controller. The Route attribute can be also applied to the methods of the controller, as you will see.

Getting a list of items

Once you have the basic infrastructure for your controller, you need to add an action to it. An action is a public method of a controller mapped to an HTTP verb. So, add a new method to the GlossaryController class as shown in the following:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        private static List<GlossaryItem> Glossary = new List<GlossaryItem> {
        //leave the glossary untouched
        };

        //new code
        [HttpGet]
        public ActionResult<List<GlossaryItem>> Get()
        {
            return Ok(Glossary);
        }
        //end new code
    }
}

The method Get() allows the client to get the whole list of glossary items. It is decorated with the HttpGet attribute which maps the method to HTTP GET requests sent to the api/glossary URL.

The return type of the method is ActionResult<List<GlossaryItem>>. This means that the method will return a List<GlossaryItem> type object or an object deriving from ActionResult type. The ActionResult type represents the HTTP status codes to be returned as a response. As said before, the ControllerBase class provides a few features to deal with HTTP requests. Some of these features are methods that create HTTP status code in a readable way, like Ok(), NotFound(), BadRequest(), and so on. The Ok(Glossary) value returned by the Get() method represents the 200 OK HTTP status code with the representation of the Glossary variable as the body of the response.

Note: An ASP.NET action may return different types: a specific type, IActionResult, or ActionResult<T>. Each return type has pros and cons. See the documentation for more information.

To test this first action implementation, run the application by typing the following command in a terminal window:

dotnet run

Note: If you are already running your web app, stop it and run it again. If you don't want manually to stop and start your application while doing changes to the source files, you can run the following command:

dotnet watch run

This causes your application to automatically restart whenever you change the source code.

Then, type the following command in another terminal window:

curl --insecure https://localhost:5001/api/glossary

The output of this command should be similar to the following:

[
  {
    "term": "Access Token",
    "definition": "A credential that can be used by an application to access an API. It informs the API that the bearer of the token has been authorized to access the API and perform specific actions specified by the scope that has been granted."
  },
  {
    "term": "JWT",
    "definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties."
  },
  {
    "term": "OpenID",
    "definition": "An open standard for authentication that allows applications to verify users are who they say they are without needing to collect, store, and therefore become liable for a user’s login information."
  }
]

Getting a single item

The next action you are going to create will allow your client to get a single glossary item. The action will expect a term as input and will provide an instance of GlossaryItem as a result.

Add a new method definition to the controller class after the previous action definition, as in the following:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        //leave the rest untouched

                //new code
        [HttpGet]
        [Route("{term}")]
        public ActionResult<GlossaryItem> Get(string term)
        {
            var glossaryItem = Glossary.Find(item => 
                    item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));

            if (glossaryItem == null)
            {
                return NotFound();
            } else
            {
                return Ok(glossaryItem);
            }
        }
        //end new code
    }
}

As you can see, you have an overloaded definition of the Get() method mapped again to the HTTP GET verb. However, in this case, you have a Route attribute for the action. This attribute appends a new variable element to the URL of the action. So, this action will respond to the HTTP request sent to the URL pattern api/glossary/{term}. The {term} part of the pattern can be replaced by any non-empty string and its value will be mapped to the term parameter.

This method checks if the requested term exists in the Glossary list and returns the glossary item. It the term doesn't exist, it returns a 404 Not Found HTTP status code.

Now, restart the application and test this action by typing the following command in a terminal window:

curl --insecure https://localhost:5001/api/glossary/jwt

The result of this command should be the definition of the JWT term:

{
  "term": "JWT",
  "definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties. "
}

Creating an item

Going ahead with the CRUD operations implementation, you need to allow the client to add a new term to the glossary. As a first step, include the System.IO namespace in the controller class by adding the using System.IO statement. The using section of the file GlossaryController.cs should look like the in the following code:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;

namespace Glossary.Controllers
{
// ... leave the rest untouched ...
}

You need the System.IO namespace to use the Path object, as you will see very soon.

Then, append the Post() method to the controller class:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        //leave the rest untouched

                //new code
        [HttpPost]
        public ActionResult Post(GlossaryItem glossaryItem)
        {
            var existingGlossaryItem = Glossary.Find(item =>
                    item.Term.Equals(glossaryItem.Term, StringComparison.InvariantCultureIgnoreCase));

            if (existingGlossaryItem != null)
            {
                return Conflict("Cannot create the term because it already exists.");
            }
            else
            {
                Glossary.Add(glossaryItem);
                var resourceUrl = Path.Combine(Request.Path.ToString(), Uri.EscapeUriString(glossaryItem.Term));
                return Created(resourceUrl, glossaryItem);
            }
        }
        //end new code
    }
}

In this case, the action is mapped to the HTTP POST verb via the HttpPost attribute. The Post() method has also a glossaryItem parameter whose value comes from the body of the HTTP POST request. Here, the method checks if the term to be created already exists. If it is so, a 409 Conflict HTTP status code will be returned. Otherwise, the new item is appended to the Glossary list. By following the REST guidelines, the action returns a 201 Created HTTP status code. The response includes the newly created item in the body and its URL in the Location HTTP header.

Run again the application and add a new glossary item by typing the following command in a terminal window:

curl --insecure \
  --verbose \
  --request POST \
  --url https://localhost:5001/api/glossary \
  --header 'content-type: application/json' \
  --data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'

As a result of executing this command you should get a long output due to the --verbose flag. If you look at the end of the output text you should see something similar to the following:

* upload completely sent off: 92 out of 92 bytes
< HTTP/1.1 201 Created
< Date: Thu, 03 Oct 2019 09:34:00 GMT
< Content-Type: application/json; charset=utf-8
< Server: Kestrel
< Transfer-Encoding: chunked
< Location: /api/glossary/MFA
< 
* Connection #0 to host localhost left intact
{"term":"MFA","definition":"An authentication process that considers multiple factors."}

Notice the 201 Created HTTP status code, the value /api/glossary/MFA for the Location header and the content of the response body. This is what you said to return at the end of the action execution.

To verify that the new item has been actually added to the glossary, type the following command at the terminal window:

curl --insecure https://localhost:5001/api/glossary/mfa

You should get the newly added item.

Note: Be sure to not stop the application between the item addition and the item existence check. Remember that the glossary is implemented as a static variable, so, when the application stops, you lose any changes.

Updating an item

After adding an item, allow the client of your Web API to update it by appending the Put() method to the controller class, as shown below:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        //leave the rest untouched

                //new code
        [HttpPut]
            public ActionResult Put(GlossaryItem glossaryItem)
            {
                var existingGlossaryItem = Glossary.Find(item =>
                item.Term.Equals(glossaryItem.Term, StringComparison.InvariantCultureIgnoreCase));

                if (existingGlossaryItem == null)
                {
                    return BadRequest("Cannot update a nont existing term.");
                } else
                {
                    existingGlossaryItem.Definition = glossaryItem.Definition;
                    return Ok();
                }
            }
        //end new code
    }
}

The Put() method is decorated with the HttpPut attribute that maps it to the HTTP PUT verb. In short, it checks if the glossary item to be updated exists in the Glossary list. If the item doesn't exist, it returns a 400 Bad Request HTTP status code. Otherwise, it updates the item's definition and returns a 200 OK HTTP status code.

Test this new action by running the application and typing the following command in a terminal window:

curl --insecure \
  --request PUT \
  --url https://localhost:5001/api/glossary \
  --header 'content-type: application/json' \
  --data '{ "term": "JWT", "definition": "An open, industry standard RFC 7519 method for representing claims securely between two parties. Auth0 uses JWT format for ID Tokens."}'

To verify if the update was successful, get the glossary item you just modified by typing the following command:

curl --insecure https://localhost:5001/api/glossary/jwt

It should return the glossary item with the new definition.

Deleting an item

Finally, you implement the deletion of an item from the glossary by appending the Delete() method to the controller class:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;

namespace Glossary.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class GlossaryController: ControllerBase
    {
        //leave the rest untouched

                //new code
        [HttpDelete]
        [Route("{term}")]
        public ActionResult Delete(string term)
        {
            var glossaryItem = Glossary.Find(item =>
                   item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));

            if (glossaryItem == null)
            {
                return NotFound();
            }
            else
            {
                Glossary.Remove(glossaryItem);
                return NoContent();
            }
        }
        //end new code
    }
}

The HttpDelete attribute maps the method Delete() to the DELETE HTTP verb. The Route attribute appends a new element to the URL the same way you learned when implemented the action that gets a single glossary item. So, when a DELETE HTTP request hits the api/glossary/{term} URL pattern, the method checks if the item exists in the Glossary list. If it doesn't exist, a 404 Not Found HTTP status code is returned. Otherwise, the Delete() method removes the item from the Glossary list and returns a 204 No Content HTTP status code.

After you had added this code, run the application and type the following command in a terminal window:

curl --insecure \
  --request DELETE \
  --url https://localhost:5001/api/glossary/openid

Then, to check that the glossary item no longer exists in the glossary, run the following command:

curl --insecure -I --request GET https://localhost:5001/api/glossary/openid

You should get a 404 Not Found response similar to the following:

HTTP/1.1 404 Not Found
Date: Thu, 03 Oct 2019 10:57:15 GMT
Content-Type: application/problem+json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked

You can check that the other glossary items still exist by getting all the items with the command you already know:

curl --insecure https://localhost:5001/api/glossary

You should get all the glossary items but the one related to openID.

Securing the API with Auth0

You completed the implementation of your glossary Web API. It allows your client to manage the terms and their definitions through the typical CRUD operations over HTTP, but maybe your system is not so safe. In fact, the current implementation allows anyone to perform any available operation on the glossary. Most probably, you want to allow everyone to get a single glossary item or the full glossary, but only authorized users should be enabled to create, update, and delete glossary items.

Take a look at how to protect these three actions by integrating your ASP.NET Core Web API with Auth0 services.

Create the API application

To start securing your API, you need to access the Auth0 Dashboard. If you haven't an Auth0 account, you can sign up for a free one. Now, move to the API section of the Dashboard and follow these steps:

  1. Click on Create API
  2. Provide a friendly name for your API (for example, Glossary API) and a unique identifier in the URL format (for example, https://glossary.com)
  3. Leave the signing algorithm to RS256 and click the Create button

These steps make Auth0 aware of your Web API and will allow you to control access.

Note: While you are in the Auth0 Dashboard, take note of your Auth0 domain, you will need it soon. This is a string in the form YOUR-TENANT-NAME.auth0.com where YOUR-TENANT-NAME is the name you provided when you created your account with Auth0. For more information, check the documentation.

Configure the Web API

Now, back in your ASP.NET Core application, open the appsettings.json configuration file and replace its content with the following:

//appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "Auth0": {
    "Domain": "YOUR_AUTH0_DOMAIN",
    "Audience": "YOUR_UNIQUE_IDENTIFIER"
  }
}

Replace the YOUR_AUTH0_DOMAIN placeholder with your Auth0 domain and the YOUR_UNIQUE_IDENTIFIER placeholder with the value you provided as a unique identifier of your API.

Integrate with Auth0

In order to interact with the Auth0 authorization services, your application needs to be able to handle tokens in the JWT (JSON Web Token) format. You can accomplish this by installing the Microsoft.AspNetCore.Authentication.JwtBearer library. Move in your application folder and type the following command in a terminal window:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

When the installation is complete, open the Startup.cs file and add the JwtBearer namespace. The using section of the file should look as shown below:

//Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;

namespace Glossary
{
// ... leave the rest untouched ...
}

In the same file, replace the ConfigureServices() method of the Startup class with the following code:

//Startup.cs

// ... leave the rest untouched ...
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    }).AddJwtBearer(options =>
    {
        options.Authority = $"https://{Configuration["Auth0:Domain"]}/";
        options.Audience = Configuration["Auth0:Audience"];
    });
    services.AddControllers();
}
// ... leave the rest untouched ...

You added the authentication service by specifying the JWT bearer scheme and providing the authority and audience values from the appsettings.json configuration file.

Finally, replace the Configure() method of the Startup class with the following code:

//Startup.cs

// ... leave the rest untouched ...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
// ... leave the rest untouched ...

Compared to the previous version, you just added the app.UseAuthentication() statement to enable authentication for your application.

Securing the endpoints

Almost everything is ready to secure your application. You just need to protect the actions that allow clients to create, update, and delete glossary items. So, open the GlossaryController.cs file in the Controllers folder and add the Microsoft.AspNetCore.Authorization namespace. The using section of the file will look like the following:

//Controllers/GlossaryController.cs
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using System.IO;
using Microsoft.AspNetCore.Authorization;

namespace Glossary.Controllers
{
// ... leave the rest untouched ...
}

Now, add the Authorize attribute to the Post(), Put(), and Delete() methods. As an example, your Delete() method should become like shown below:

//Controllers/GlossaryController.cs
...
        [HttpDelete]
        [Route("{term}")]
        [Authorize]
        public ActionResult Delete(string term)
        {
            var glossaryItem = Glossary.Find(item =>
                   item.Term.Equals(term, StringComparison.InvariantCultureIgnoreCase));

            if (glossaryItem == null)
            {
                return NotFound();
            }
            else
            {
                Glossary.Remove(glossaryItem);
                return NoContent();
            }
        }
...

As you can see, the only difference is the Authorize attribute.

Note: You may be wondering if the order of the attributes applied to a method or to a controller class matters. Their order is internally defined and it depends on the stage of the HTTP pipeline they are bound to and on their scope (global, class or method scope). So, from a syntactical point of view, it doesn't matter if you put, for example, the Authorize attribute before or after the Route one. For more information on this point, check the official documentation.

Testing the Secure Web API

In order to ensure that all works as expected, run the application and check if the not protected actions still work as before. So, type the following command in a terminal window:

curl --insecure https://localhost:5001/api/glossary/jwt

You should get again the glossary item corresponding to the JWT term.

Now, try to add a new item to the glossary by typing again the following command:

curl --insecure \
  --verbose \
  --request POST \
  --url https://localhost:5001/api/glossary \
  --header 'content-type: application/json' \
  --data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'

This time you should get a 401 Unauthorized HTTP status code. In fact, the last part of your output should be similar to the following:

< HTTP/1.1 401 Unauthorized
< Date: Thu, 03 Oct 2019 15:20:15 GMT
< Server: Kestrel
< Content-Length: 0
< WWW-Authenticate: Bearer

This means that your action is protected. So, in order to add a new item to the glossary, you need to get an access token from Auth0. The simplest way is to access again the API section of your Auth0 Dashboard, select the API you created before and select the Test tab. In this section, you can get a temporary token to test your Web API by clicking the Copy Token link as shown in the following picture:

Now, head back to your terminal window and type the following command, where the YOUR_TOKEN placeholder must be replaced with the copied token:

curl --insecure \
  --verbose \
  --request POST \
  --url https://localhost:5001/api/glossary \
  --header 'content-type: application/json' \
  --header 'authorization: Bearer YOUR_TOKEN' \
  --data '{ "term": "MFA", "definition": "An authentication process that considers multiple factors."}'

Now you should get a successful 201 Created HTTP status code as before securing the API.

To ensure that actually the new glossary item has been added, run the following command:

curl --insecure https://localhost:5001/api/glossary/mfa

It should return the newly added glossary item. Now your Web API is ready to be securely consumed by clients.

Recap

In this article, you learned to create a new Web API project with ASP.NET Core 3.0. You started by exploring the sample project generated by the CLI command dotnet new and continued by replacing the existing code with yours. You implemented the CRUD operations on a simple glossary and learned how to map methods to HTTP verbs by leveraging the proper attributes. Also, you discovered how ASP.NET Core lets you return HTTP responses in a readable way. Finally, you secured a subset of the CRUD operations by integrating the Web API application with Auth0.

You can download the full source code of the application from GitHub.