Nowadays, RESTful APIs are the standard way of exposing backends to applications.

They allow you to share your business logic between different clients with a low level of coupling through a super-standardized protocol: HTTP.

One of the biggest challenges when building REST API is authentication. Typically, we manage this with JWTs. Unfortunately, ASP.NET Core doesn’t fully support this out-of-the-box.

The good news is that the Stormpath ASP.NET Core library allows us to add JWT authentication to any API with minimal configuration.

In this tutorial, we will create a REST API in ASP.NET Core to manage a list of books.

Our example API will allow users to register and login to manage their books. This simple project could be the base for a future social media application that connects readers and supports book reviews.

The source code is available on GitHub, so feel free to check the finished code and play with it.

Let’s get started!

Create the Web API Project

Open up Visual Studio, and create a new ASP.NET Core Web Application project.

Select the “Web API” template and make sure authentication is set to “No Authentication”.

Now, we are going to create our Book model. Add a folder named “Models” at the root of the project, and then inside of it create the Book class:

public class Book { public int Id { get; set; } public string Title { get; set; } public string Author { get; set; } public DateTime PublishedDate { get; set; } } 1 2 3 4 5 6 7 8 public class Book { public int Id { get ; set ; } public string Title { get ; set ; } public string Author { get ; set ; } public DateTime PublishedDate { get ; set ; } }

Create a new folder named “Services”. To save our books, we are going to use a useful new great feature available in EF Core: the in-memory data provider. This feature is awesome because we don’t have to spend time setting up a database to test our API. Later on, we can easily swap this provider with one that uses a persistent storage like a database, for example.

If you’re interesting in exploring EF Core as an in-memory data provider further, check out Nate’s article on the subject!

Set Up Entity Framework Core

Right-click on your project and select “Manage NuGet packages”. Then, add the package Microsoft.EntityFrameworkCore.InMemory

Add the BooksAPIContext class inside the Models folder, which will implement the DbContext class and will be responsible for the interactions between our application and the data provider.

public class BooksAPIContext : DbContext { public BooksAPIContext(DbContextOptions options) : base(options) { } public DbSet<Book> Books { get; set; } } 1 2 3 4 5 6 7 8 9 10 public class BooksAPIContext : DbContext { public BooksAPIContext ( DbContextOptions options ) : base ( options ) { } public DbSet < Book > Books { get ; set ; } }

On the ConfigureServices method of the Startup class, we are going to configure our context to use the in-memory data provider:

// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BooksAPIContext>(x => x.UseInMemoryDatabase()); // Add framework services. services.AddMvc(); } 1 2 3 4 5 6 7 8 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices ( IServiceCollection services ) { services . AddDbContext < BooksAPIContext > ( x = > x . UseInMemoryDatabase ( ) ) ; // Add framework services. services . AddMvc ( ) ; }

Create the IBookRepository interface inside of the Services folder:

public interface IBookRepository { Book Add(Book book); IEnumerable<Book> GetAll(); Book GetById(int id); void Delete(Book book); void Update(Book book); } 1 2 3 4 5 6 7 8 9 public interface IBookRepository { Book Add ( Book book ) ; IEnumerable < Book > GetAll ( ) ; Book GetById ( int id ) ; void Delete ( Book book ) ; void Update ( Book book ) ; }

And a concrete InMemoryBookRepository class that will use the BookAPIContext to interact with the in-memory database:

public class InMemoryBookRepository : IBookRepository { private readonly BooksAPIContext _context; public InMemoryBookRepository(BooksAPIContext context) { _context = context; } public Book Add(Book book) { var addedBook = _context.Add(book); _context.SaveChanges(); book.Id = addedBook.Entity.Id; return book; } public void Delete(Book book) { _context.Remove(book); _context.SaveChanges(); } public IEnumerable<Book> GetAll() { return _context.Books.ToList(); } public Book GetById(int id) { return _context.Books.SingleOrDefault(x => x.Id == id); } public void Update(Book book) { var bookToUpdate = GetById(book.Id); bookToUpdate.Author = book.Author; bookToUpdate.Title = book.Title; bookToUpdate.PublishedDate = book.PublishedDate; _context.Update(bookToUpdate); _context.SaveChanges(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class InMemoryBookRepository : IBookRepository { private readonly BooksAPIContext _context ; public InMemoryBookRepository ( BooksAPIContext context ) { _context = context ; } public Book Add ( Book book ) { var addedBook = _context . Add ( book ) ; _context . SaveChanges ( ) ; book . Id = addedBook . Entity . Id ; return book ; } public void Delete ( Book book ) { _context . Remove ( book ) ; _context . SaveChanges ( ) ; } public IEnumerable < Book > GetAll ( ) { return _context . Books . ToList ( ) ; } public Book GetById ( int id ) { return _context . Books . SingleOrDefault ( x = > x . Id == id ) ; } public void Update ( Book book ) { var bookToUpdate = GetById ( book . Id ) ; bookToUpdate . Author = book . Author ; bookToUpdate . Title = book . Title ; bookToUpdate . PublishedDate = book . PublishedDate ; _context . Update ( bookToUpdate ) ; _context . SaveChanges ( ) ; } }

Don’t forget to register the repository as an injectable service within the ConfigureService in the Startup class:

// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddDbContext<BooksAPIContext>(x => x.UseInMemoryDatabase()); services.AddTransient<IBookRepository, InMemoryBookRepository>(); // Add framework services. services.AddMvc(); } 1 2 3 4 5 6 7 8 9 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices ( IServiceCollection services ) { services . AddDbContext < BooksAPIContext > ( x = > x . UseInMemoryDatabase ( ) ) ; services . AddTransient < IBookRepository , InMemoryBookRepository > ( ) ; // Add framework services. services . AddMvc ( ) ; }

Now that you have set up your data layer let’s dive into the Web API controller!

Create the Note Web API Controller

Before going any further, make sure to delete the boilerplate ValuesController that the framework created automatically. Also, modify the launchSettings.json file and make sure the launch URL of the profile you are using is pointing to a valid URL. In this example, we will point to our book controller:

{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:63595/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "book", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "BooksAPI": { "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:5000/book", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 { "iisSettings" : { "windowsAuthentication" : false , "anonymousAuthentication" : true , "iisExpress" : { "applicationUrl" : "http://localhost:63595/" , "sslPort" : 0 } } , "profiles" : { "IIS Express" : { "commandName" : "IISExpress" , "launchBrowser" : true , "launchUrl" : "book" , "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } , "BooksAPI" : { "commandName" : "Project" , "launchBrowser" : true , "launchUrl" : "http://localhost:5000/book" , "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } }

Create a Web API Controller Class in the Controllers folder and name it BookController .

The auto-generated code will look like this:

[Route("api/[controller]")] public class BookController : Controller { // GET: api/values [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/values/5 [HttpGet("{id}")] public string Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody]string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody]string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [ Route ( "api/[controller]" ) ] public class BookController : Controller { // GET: api/values [ HttpGet ] public IEnumerable < string > Get ( ) { return new string [ ] { "value1" , "value2" } ; } // GET api/values/5 [ HttpGet ( "{id}" ) ] public string Get ( int id ) { return "value" ; } // POST api/values [ HttpPost ] public void Post ( [ FromBody ] string value ) { } // PUT api/values/5 [ HttpPut ( "{id}" ) ] public void Put ( int id , [ FromBody ] string value ) { } // DELETE api/values/5 [ HttpDelete ( "{id}" ) ] public void Delete ( int id ) { } }

The framework automatically generated a lot of code for us. There is a method for each HTTP verb that our controller will handle. As a refresher, the REST API standard uses each HTTP verb for a different action over our resources:

You will also see the controller has a Route attribute, with the value api/[controller] . This defines the base route for all of this controller endpoints, which in this case is api/book . We will change this to make the base route book alone. It should look like this:

Route(“[controller]”)

This attribute can also be applied at method-level if you need to define custom routes for a specific endpoint.

The Get method (as well as the Put and Delete methods) have in their HTTP verb Attribute an “id” element:

HttpGet("{id}")

This is a placeholder for the “id” parameter in the URL of the endpoint. For example, for the Get method, the URL will be:

/book/{id}

The framework automagically maps the parameters defined in these attributes to the parameters of the method in the controller. Awesome, huh?

We will now write the code to handle each request to our API:

[Route("[controller]")] public class BookController : Controller { private readonly IBookRepository _bookRepository; public BookController(IBookRepository bookRepository) { _bookRepository = bookRepository; } // GET: book [HttpGet] public IEnumerable<Book> Get() { return _bookRepository.GetAll(); } // GET book/5 [HttpGet("{id}", Name = "GetBook")] public IActionResult Get(int id) { var book = _bookRepository.GetById(id); if (book == null) { return NotFound(); } return Ok(book); } // POST book [HttpPost] public IActionResult Post([FromBody]Book value) { if (value == null) { return BadRequest(); } var createdBook = _bookRepository.Add(value); return CreatedAtRoute("GetBook", new { id = createdBook.Id }, createdBook); } // PUT book/5 [HttpPut("{id}")] public IActionResult Put(int id, [FromBody]Book value) { if (value == null) { return BadRequest(); } var note = _bookRepository.GetById(id); if (note == null) { return NotFound(); } value.Id = id; _bookRepository.Update(value); return NoContent(); } // DELETE book/5 [HttpDelete("{id}")] public IActionResult Delete(int id) { var book = _bookRepository.GetById(id); if (book == null) { return NotFound(); } _bookRepository.Delete(book); return NoContent(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 [ Route ( "[controller]" ) ] public class BookController : Controller { private readonly IBookRepository _bookRepository ; public BookController ( IBookRepository bookRepository ) { _bookRepository = bookRepository ; } // GET: book [ HttpGet ] public IEnumerable < Book > Get ( ) { return _bookRepository . GetAll ( ) ; } // GET book/5 [ HttpGet ( "{id}" , Name = "GetBook" ) ] public IActionResult Get ( int id ) { var book = _bookRepository . GetById ( id ) ; if ( book == null ) { return NotFound ( ) ; } return Ok ( book ) ; } // POST book [ HttpPost ] public IActionResult Post ( [ FromBody ] Book value ) { if ( value == null ) { return BadRequest ( ) ; } var createdBook = _bookRepository . Add ( value ) ; return CreatedAtRoute ( "GetBook" , new { id = createdBook . Id } , createdBook ) ; } // PUT book/5 [ HttpPut ( "{id}" ) ] public IActionResult Put ( int id , [ FromBody ] Book value ) { if ( value == null ) { return BadRequest ( ) ; } var note = _bookRepository . GetById ( id ) ; if ( note == null ) { return NotFound ( ) ; } value . Id = id ; _bookRepository . Update ( value ) ; return NoContent ( ) ; } // DELETE book/5 [ HttpDelete ( "{id}" ) ] public IActionResult Delete ( int id ) { var book = _bookRepository . GetById ( id ) ; if ( book == null ) { return NotFound ( ) ; } _bookRepository . Delete ( book ) ; return NoContent ( ) ; } }

Now we’re ready to test our API!

Add JWT authentication using Stormpath

So far this is a totally public API, so any user can get, create, edit, and delete any book they want. That’s not very secure! We will now add authentication to our API through JWT tokens.

If you want to refresh your knowledge, check out our overview of token authentication and JWTs!

As today, ASP.NET Core supports protecting routes with Bearer header JWTs. But, unlike the ASP.NET 4.x Web API framework, it doesn’t have support for issuing them. To do this, you will need to write custom middleware or use external packages. There are several options; you can read Nate’s article to learn more about this.

Lucky for us, Token Authentication with JWT becomes extremely easy using the Stormpath ASP.NET Core library – I’ll show you how.

Get your Stormpath API credentials

To communicate with Stormpath, your application needs a set of API Keys. Grab them from your Stormpath account (If you haven’t already registered for Stormpath, you can create a free developer account here).

Once you have them, you should store them in environment variables. Open up the command line and execute these commands:

setx STORMPATH_CLIENT_APIKEY_ID "<your_api_key_id>" setx STORMPATH_CLIENT_APIKEY_SECRET "<your_api_key_secret>" 1 2 3 setx STORMPATH_CLIENT_APIKEY_ID "<your_api_key_id>" setx STORMPATH_CLIENT_APIKEY_SECRET "<your_api_key_secret>"

Restart Visual Studio to pick up the environment variables from your OS.

Integrate Stormpath with the Web API

Right-click on your project and select “Manage NuGet packages”. Them, add the package Stormpath.AspNetCore .

To use Stormpath API for Access Token authentication, add this configuration in the ConfigureServices method in the Startup.cs .

// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddStormpath(new StormpathConfiguration() { Web = new WebConfiguration() { // This explicitly tells the Stormpath middleware to only serve JSON responses (appropriate for an API). // By default, HTML responses are served too. Produces = new[] {"application/json"}, Oauth2 = new WebOauth2RouteConfiguration() { Uri = "/token", } } }); ... } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices ( IServiceCollection services ) { services . AddStormpath ( new StormpathConfiguration ( ) { Web = new WebConfiguration ( ) { // This explicitly tells the Stormpath middleware to only serve JSON responses (appropriate for an API). // By default, HTML responses are served too. Produces = new [ ] { "application/json" } , Oauth2 = new WebOauth2RouteConfiguration ( ) { Uri = "/token" , } } } ) ; . . . }

As a personal preference, I changed the default token endpoint URI ( "/oauth/token" ) to /token .

Options that are not overridden by explicit configuration will retain their default values.

Make sure you add the Stormpath middleware before any middleware that requires protection, such as MVC.

You can learn more about configuration options in the Stormpath Product Documentation.

Now, find the Configure method and add Stormpath to your middleware pipeline.

// 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.UseStormpath(); app.UseMvc(); } 1 2 3 4 5 6 7 8 9 // 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 . UseStormpath ( ) ; app . UseMvc ( ) ; }

Finally, to effectively protect the controller, add the Authorize attribute:

[Authorize] [Route("[controller]")] public class BookController : Controller { ... } 1 2 3 4 5 6 7 [ Authorize ] [ Route ( "[controller]" ) ] public class BookController : Controller { . . . }

That’s all! Exhausted yet? 😉

Test Your Web API with Postman

Now, let’s test our Web API. I’m using Postman for this tutorial, but feel free to use any REST client you like. To register a new user, we need to make a POST request to the /register endpoint, passing the required data on the body:

{ "givenName": "MyAPIUser", "surname": "Tester", "email": "[email protected]", "password": "TestTest1" }

With our user, we are going to get a token by POSTing to the /token endpoint.

Payload should be a URL-encoded form with your credentials:

grant_type: password username: test3@example.com password: TestTest1 1 2 3 4 grant_type : password username : test3 @ example . com password : TestTest1

The access token you received should be sent to the server on every request, on the Authorization header. The value of the header should be Bearer <your_token> :

Let’s add a new book:

The status code 201 indicates our application has created a new book successfully.

If you view your list of books now, you should see the one you just created has been added:

Congratulations! You just created a web API with ASP.NET Core. 🙂

Learn More