Discover and enable the integrations you need to solve identityAuth0 Marketplace
Spring Boot

Build and Secure an API with Spring Boot and Kotlin

See how easy it is to bootstrap a CRUD API with Spring Initializr, build it with Spring Boot and Kotlin, and secure it with Auth0.

August 11, 2021

Spring Boot

Build and Secure an API with Spring Boot and Kotlin

See how easy it is to bootstrap a CRUD API with Spring Initializr, build it with Spring Boot and Kotlin, and secure it with Auth0.

August 11, 2021

TL;DR: In this article, you’ll learn how to quickly build a RESTful API using the Spring Boot framework and the Kotlin programming language. You’ll then use Auth0 to secure the API so that only authorized parties can use its key functions. You can find the final code for the project developed throughout this article in this GitHub repository.

Look for the 🌶 emoji if you’d like to skim through the content while focusing on the build and execution steps.

Spring Framework, Spring Boot, and Kotlin: An Overview

Before we start building the project, let’s look at the frameworks and language you’ll use.

Spring Framework

Spring Framework icon

Spring Framework, often shortened to just “Spring”, is a popular open source application framework for the Java platform. It’s built on the principle of inversion of control, whose name implies that control of a Spring application is the opposite of how program control works in traditional applications. Instead of your code directing the application and calling on functionality from a library, the framework directs the application and calls on functionality from your code.

This approach makes it possible to write an application as a collection of components, with the connections between them maintained by Spring. Spring provides a lot of ready-built components called dependencies that provide functionality that applications commonly require. The end result is a system for building applications that are modular, flexible, quicker to write, and easy to maintain.

Spring is often described — especially by Java developers, who’ve grown used to working with unwieldy frameworks — as lightweight. It provides a set of built-in often-needed capabilities such as MVC, caching, messaging, security, and data access. It significantly reduces the amount of code you have to write and lets you focus on the functionality specific to your application.

While Spring is often used for web and server-based applications, it’s a general application framework and it can be used to write desktop and mobile applications as well.

Spring Boot

Spring Boot icon

Spring Boot is a framework built on top of Spring that simplifies Spring development. It does this by enforcing an opinionated approach to Spring application development through “convention over configuration”, the use of default setups that applications are likely to use, and a standardized application structure created by a generator that goes by the decidedly “Web 2.0” name of Spring Initializr. Thanks to this “it just works” approach, it’s the fastest way to develop applications with the Spring framework.

Kotlin

Kotlin icon

Kotlin is part of the wave of programming languages that appeared in the 2010s, which includes Dart, Go, Rust, and Swift. These languages have a number of common traits including their own takes on object-oriented programming from the lessons of the 1990s and functional programming from the lessons of the 2000s, inferred strong static typing, and meteoric rises in popularity (all of them are in TIOBE’s index of popular programming languages, and all of them except Kotlin are in the top 20).

As a programming language designed by a company that makes developer tools, Kotlin is unique in its origin. It was designed to be an improvement on Java, with many of the language features of Scala (but faster compile times) and with the power and convenience that comes from interoperability with Java and the Java platform. It has grown to become the preferred language for Android app development; as of July 2021, 80% of the top 1,000 apps in Google Play were written in Kotlin. According to JetBrains’ State of Developer Ecosystem 2020 report, Kotlin is also making serious inroads into web and enterprise development territory.

Since Spring and Spring Boot are based on the Java platform, you can use them to build applications using any JVM-based programming language, including Kotlin. If Java’s verbosity is bringing you down, or if you’re an Android developer who wants to write the back end for your app but don’t want to switch languages, Spring and Spring Boot development in Kotlin is for you!

What You’ll Build: A Hot Sauce API

A Hot Sauce API (A tray of hot sauce bottles, viewed from above))

You’ll build an API that acts as a catalog of hot sauces. It will be a simple one, exposing a single resource named hotsauces.

The API will provide endpoints for:

  • Confirming that it is operational
  • Getting a list of all the hot sauces in the catalog
  • Getting the number of hot sauces in the catalog
  • Adding a hot sauce to the catalog
  • Editing any hot sauce in the catalog
  • Deleting a hot sauce from the catalog

In the first part of the project, you’ll build the API. Once built, you’ll secure it so that the endpoints for CRUD operations will require authentication. The endpoint for testing to see if the API is operational will remain public.

Prerequisites

To follow along with this article, you’ll need the following installed on your local machine:

  • JDK 11
  • You’ll need some command-line tool for issuing HTTP requests. The examples in this article will use the following:
    • For macOS and Linux, the curl command line tool
    • For Windows, PowerShell and its Invoke-RestMethod and Invoke-WebRequest cmdlets.
  • Your favorite code editor

You’ll also need internet access, as you’ll be using the Spring Initializr web page and the Gradle build tool, which goes online to download project dependencies.

Building the API

Scaffolding with Spring Initializr

The preferred way to set up a new Spring Boot project is to use Spring Initializr, a web application that generates a basic Spring Boot project, complete with all the necessary configurations, your choice of dependencies, and Gradle or Maven build files. It’s available online at start.spring.io and built into the Ultimate Edition of IntelliJ IDEA.

You could create a new project manually using Spring Boot’s command-line interface, but Spring Initializr organizes its projects using a standardized directory layout for JVM-based projects. This makes your projects easier to maintain, and lets you focus on what your application actually does.

🌶 Point your favorite browser at the Spring Initializr page:

The Spring Initializr webpage

Here’s how you should fill it out:

  • Project: Select Gradle Project.
  • Language: Select Kotlin.
  • Spring Boot: Select the latest stable version. At the time of writing, it’s version 2.5.3.
  • Project Metadata:
    • Group: This should be something that uniquely identifies yourself or your organization. Typically, you’d enter your domain name in reverse notation (for example, if your domain is abc123.com, enter com.abc123. If you don’t have a domain name, just enter com.auth0.
    • Artifact: This should be something that identifies the project. Enter hotsauces.
    • Name: This will autofill based on what you entered for Artifact; just use the value in this field.
    • Description: Enter A Spring Boot/Auth0 API project into this field.
    • Package name: This will autofill based on what you entered for Group and Artifact; just use the value in this field.
    • Packaging: Select Jar.
    • Java: Select 11.
  • Dependencies: Click the ADD DEPENDENCIES... button and choose the following:
    • Spring Boot DevTools: A set of convenient tools for development.
    • Spring Web: Provides a web server and facilities for building RESTful web applications and APIs.
    • Spring Data JPA: Makes the process of building a data access layer almost trivial.
    • H2 Database: This project will use the H2 in-memory database.
    • Spring Boot Actuator: Adds a handy collection of diagnostic tools to a Spring Boot project.

Once filled out, the form should look like this:

The Spring Initializr webpage, filled out with this project's details

🌶 With the form filled out, click the GENERATE button. The site will generate a .zip file, and it will automatically be downloaded to your local machine. Unzip the file to reveal the hotsauces project folder.

Within the hotsauces folder, you’ll work in two specific areas for this exercise:

  • /src/main/kotlin/com/auth0/hotsauces/: The code for the project goes into this directory. At the moment, it contains a single file, HotSaucesApplication.kt. This contains a simple application class, which is the first code in the project that is executed when the application is run.
  • /build.gradle.kts: This is the project’s Gradle build file. Among other things, it specifies which dependencies will be used by the project. You’ll add some security-related dependencies to this file when securing the API.

Defining HotSauces, the Class Behind the API’s Resource

A tray of different bottles of hot sauce

The first piece of code you’ll write is the model behind the API’s only resource: hot sauces.

Each hot sauce has the following attributes:

  • id — (number) The unique identifier for the hot sauce, and primary key.
  • brandName — (string) The name of the producer of the hot sauce.
  • sauceName — (string) The name of the hot sauce.
  • description — (string) A description of the hot sauce, with the appropriate keywords. This may be lengthy.
  • url — (number) The URL for the web page for the hot sauce.
  • heat — (number) The spiciness of the hot sauce, expressed in Scoville heat units (SHUs).

🌶 Create a new file named HotSauce.kt in the /src/main/kotlin/com/auth0/hotsauces/ directory:

// /src/main/kotlin/com/auth0/hotsauces/HotSauce.kt

package com.auth0.hotsauces

import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Lob

@Entity
data class HotSauce(

    // This property maps to the primary key in the database.
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    val id: Long = 0,

    // These properties are NOT likely to be longer than 256 characters.
    var brandName: String = "",
    var sauceName: String = "",

    // These properties might be longer than 256 characters.
    @Lob
    var description: String = "",
    @Lob
    var url: String = "",

    var heat: Int = 0

)

HotSauce is a Kotlin data class, whose job is to hold data. Data classes are all properties and no methods — or at least no explicitly defined methods, anyway. The compiler automatically provides a handful of “behind the scenes” methods to data classes, including equals(), hashCode(), and toString().

Kotlin classes, data classes included, have a constructor built into the class header. This constructor, called the primary constructor, takes its parameters in the parentheses that follow the class name. HotSauce, like many data classes, is just a class header and primary constructor, and nothing else.

Even though there isn’t much to the class, it uses some annotations to pull in a lot of extra functionality:

  • HotSauce is annotated with @Entity, which informs the Java Persistence API (JPA, one of the dependencies that you added in Spring Initializr) that its instances will be persisted in the database.
  • The id property is annotated with both @Id and @GeneratedValue, which makes sense, as it will map to the id field in the corresponding database table.
  • The @Lob annotation is short for “large object”. It’s being used to annotate the description and url properties because they could contain strings longer than 256 characters. By default, JPA maps Strings in entities to the VARCHAR(256) type in the database; marking a String as @Lob tells JPA to map it to the TEXT type instead.

If you’re new to Kotlin, make note of the keywords in the class’ property declarations:

  • val is short for “value” and declares a constant. A hot sauce’s id value shouldn’t be changed after it’s been created, so we’re declaring it with val.
  • var is short for “variable” and that’s what it declares. All the other class properties should be editable, so we’re declaring them with var.

You might be wondering about assigning the value 0 to id, which is a constant:

@Id @GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = 0,

The line val id: Long = 0, might make you think that every hot sauce will have an unchangeable id set to 0. As a constant, the value of id can’t be changed by any code that you write, but the @GeneratedValue annotation allows Spring to use reflection to get around that limitation and assign a unique id value for every sauce. Every modern application framework incorporates “magic” like this; I’ve found that it’s simplest just to know where such instances are and to accept them.

With HotSauce defined, you have a model that represents hot sauces in the real world. By annotating it with @Entity, you’ve specified that instances of HotSauce should be converted into entities — that is, instances that have been saved to a database.

It’s time to set up something to save HotSauce instances.

Adding Data Access with a CrudRepository

The Repository pattern provides a layer of abstraction between an application’s models and the system used to store data. A repository provides a collection-style interface to the models, with methods for retrieving the whole collection, retrieving a specific item, and adding, editing, and deleting items. The repository insulates the models from the usual database concerns, such as connecting with it, setting up a reader, or worrying about things like cursors.

Spring provides a number of repository interfaces. In this project, you’ll use the CrudRepository interface to act as the intermediary between HotSauce and the H2 database (another one of the dependencies you added in Spring Initializr) as shown below:

Repository Pattern

In this exercise, the underlying data store is the H2 in-memory database. The Repository pattern makes it so that changing the database doesn’t require you to make any changes to HotSauce, and Spring’s inversion of control architecture makes it so that such a change doesn’t even require a change to the CrudRepository.

🌶 Create a new file named HotSauceRepository.kt in the /src/main/kotlin/com/auth0/hotsauces/ directory:

// /src/main/kotlin/com/auth0/hotsauces/HotSauceRepository.kt

package com.auth0.hotsauces

import org.springframework.data.repository.CrudRepository

interface HotSauceRepository: CrudRepository<HotSauce, Long>

This code sets up an interface named HotSauceRepository that’s based on a CrudRepository of entities based on the HotSauce model, each of which is uniquely identified by a Long (namely, the id property of HotSauce).

CrudRepository provides a set of methods for performing the standard set of CRUD operations. Here’s the subset that you’ll use in this project:

  • count() — Returns the number of entities.
  • deleteById({id}) — Deletes the entity with the given id.
  • existsById({id}) — Returns true if the entity with the given id exists.
  • findAll() — Retrieves all the entities.
  • findById({id}) — Retrieves the entity with the given id.
  • save() — Saves the given entity.

You’ll use these methods in the controller, which you’ll build next.

Note that you didn’t have to do anything to connect the repository to the database. Spring Boot’s inversion of control took care of that for you.

Building the controller

The controller should expose the following API endpoints:

  • GET api/hotsauces: Returns the entire collection of hot sauces. Accepts these optional named parameters:
    • brandname: Limits the results to only those sauces whose brandName contains the given string.
    • saucename Limits the results to only those sauces whose sauceName contains the given string.
    • desc Limits the results to only those sauces whose description contains the given string.
    • minheat Limits the results to only those sauces whose heat rating is greater than or equal to the given number.
    • maxheat Limits the results to only those sauces whose heat rating is less than or equal to the given number.
  • GET api/hotsauces/{id}: Returns the hot sauce with the given id.
  • GET api/hotsauces/count: Returns the number of hot sauces.
  • POST api/hotsauce: Adds a hot sauce whose details are included in the request.
  • PUT api/hotsauces/{id}: Updates the hot sauce with the given id using the details included in the request.
  • DELETE api/hotsauces/{id}: Deletes the hot sauce with the given id.

🌶 Create a new file named HotSauceController.kt in the /src/main/kotlin/com/auth0/hotsauces/ directory:

// /src/main/kotlin/com/auth0/hotsauces/HotSauceController.kt

package com.auth0.hotsauces

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.util.*

@RestController
@RequestMapping("/api/hotsauces")
class HotSauceController(private val hotSauceRepository: HotSauceRepository) {

    // GET api/hotsauces
    @GetMapping("")
    fun getAll(@RequestParam(value="brandname", required = false, defaultValue = "") brandNameFilter: String,
               @RequestParam(value="saucename", required = false, defaultValue = "") sauceNameFilter: String,
               @RequestParam(value="desc", required = false, defaultValue = "") descFilter: String,
               @RequestParam(value="minheat", required = false, defaultValue = "") minHeat: String,
               @RequestParam(value="maxheat", required = false, defaultValue = "") maxHeat: String): ResponseEntity<List<HotSauce>> {
        val MAX_SCOVILLE = 3_000_000 // At this point, it's no longer food, but a weapon
        val minHeatFilter = if (!minHeat.isNullOrBlank()) minHeat.toInt() else 0
        val maxHeatFilter = if (!maxHeat.isNullOrBlank()) maxHeat.toInt() else MAX_SCOVILLE
        return ResponseEntity(hotSauceRepository.findAll()
            .filter { it.brandName.contains(brandNameFilter, true) }
            .filter { it.sauceName.contains(sauceNameFilter, true) }
            .filter { it.description.contains(descFilter, true) }
            .filter { it.heat >= minHeatFilter }
            .filter { it.heat <= maxHeatFilter },
            HttpStatus.OK
        )
    }

    // GET api/hotsauces/{id}
    @GetMapping("/count")
    fun getCount(): ResponseEntity<Long> = ResponseEntity(hotSauceRepository.count(),
            HttpStatus.OK)

    // GET api/hotsauces/count
    @GetMapping("/{id}")
    fun getHotSauce(@PathVariable id: Long): ResponseEntity<Optional<HotSauce>> {
        if (hotSauceRepository.existsById(id)) {
            return ResponseEntity(hotSauceRepository.findById(id), HttpStatus.OK)
        } else {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

    // POST api/hotsauce
    @PostMapping()
    fun createHotSauce(@RequestBody hotSauce: HotSauce): ResponseEntity<HotSauce> {
        return ResponseEntity(hotSauceRepository.save(hotSauce), HttpStatus.CREATED)
    }

    // PUT api/hotsauces/{id}
    @PutMapping("/{id}")
    fun updateHotSauce(@PathVariable id: Long, @RequestBody sauceChanges: HotSauce): ResponseEntity<HotSauce?> {
        if (hotSauceRepository.existsById(id)) {
            val originalSauce = hotSauceRepository.findById(id).get()
            val updatedSauce = HotSauce(
                id = id,
                brandName = if (sauceChanges.brandName != "") sauceChanges.brandName else originalSauce.brandName,
                sauceName = if (sauceChanges.sauceName != "") sauceChanges.sauceName else originalSauce.sauceName,
                description = if (sauceChanges.description != "") sauceChanges.description else originalSauce.description,
                url = if (sauceChanges.url != "") sauceChanges.url else originalSauce.url,
                heat = if (sauceChanges.heat != 0) sauceChanges.heat else originalSauce.heat
            )
            return ResponseEntity(hotSauceRepository.save(updatedSauce), HttpStatus.OK)
        } else {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

    // DELETE api/hotsauces/{id}
    @DeleteMapping("/{id}")
    fun deleteHotSauce(@PathVariable id: Long): ResponseEntity<HotSauce?> {
        if (hotSauceRepository.existsById(id)) {
            hotSauceRepository.deleteById(id)
            return ResponseEntity(HttpStatus.NO_CONTENT)
        } else {
            return ResponseEntity(HttpStatus.NOT_FOUND)
        }
    }

}

The code is considerably less complex than it could be, thanks to some annotations which take care of a lot of tedious REST work:

  • HotSauceController is annotated with @RestController, which informs Spring Web (yet another one of the dependencies that you added in Spring Initializr) that this class is a REST controller and that it should include the necessary underlying REST functionality.
  • HotSauceController is also annotated with @RequestMapping("/api/hotsauces") which means that every method in the class that responds to a request responds to requests whose endpoint ends with api/hotsauces.
  • Any method annotated with @GetMapping() responds to GET requests. If @GetMapping() takes a parameter, it means that it responds to requests whose endpoint begins with that parameter. Parameters in braces ({ and }) are variable parameters.
  • Any method annotated with @PostMapping(), @PutMapping(), and @DeleteMapping() is similar to a method annotated with @GetMapping() except that they respond to POST, PUT, and DELETE requests respectively.

Every method in the class returns a ResponseEntity object, which represents a complete HTTP response, complete with status code, headers, and body.

Take a look at each of these methods to see what kind of ResponseEntity they return. Sometimes, they’ll simply return an HTTP status code, as is the case with deleteHotSauce(), which returns either NO_CONTENT if the hot sauce was found and deleted or NOT_FOUND if the hot sauce requested for deletion was not in the database. Other methods may return a more complex ResponseEntity, such as getHotSauce(), which returns an OK status code and a HotSauce object if the given id matches a hot sauce in the database.

You may have noticed the @RequestParam annotations in the signature of the getAll() method. These map the parameters in the HTTP GET query string to getAll()’s parameters. The first annotation declares that value in brandname query parameter is mapped to the getAll() method’s brandNameFilter parameter, that brandname is an optional query parameter, and if it isn’t used, its value should default to the empty string. The other @RequestParam annotations work in a similar manner.

Initializing the database

You could run the app right now and it would work. There’s just one problem: Since the database is in-memory and unitialized, you don’t have any data to work with! Let’s add a class to preload the database with some initial hot sauces.

🌶 Create a new file named DataLoader.kt in the /src/main/kotlin/com/auth0/hotsauces/ directory:

// /src/main/kotlin/com/auth0/hotsauces/DataLoader.kt

package com.auth0.hotsauces

import org.springframework.stereotype.Component
import javax.annotation.PostConstruct

@Component
class DataLoader(var hotSauceRepository: HotSauceRepository) {

    fun String.trimIndentsAndRemoveNewlines() = this.trimIndent().replace("\n", " ")

    @PostConstruct
    fun loadData() {
        hotSauceRepository.saveAll(listOf(
            HotSauce(
                brandName = "Truff",
                sauceName = "Hot Sauce",
                description = """
                    Our sauce is a curated blend of ripe chili peppers, organic agave nectar, black truffle, and
                    savory spices. This combination of ingredients delivers a flavor profile unprecedented to hot sauce.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://truffhotsauce.com/collections/sauce/products/truff",
                heat = 2_500
            ),
            HotSauce(
                brandName = "Truff",
                sauceName = "Hotter Sauce",
                description = """
                    TRUFF Hotter Sauce is a jalapeño rich blend of red chili peppers, Black Truffle and Black Truffle
                    Oil, Organic Agave Nectar, Red Habanero Powder, Organic Cumin and Organic Coriander. Perfectly
                    balanced and loaded with our same iconic flavor, TRUFF Hotter Sauce offers a “less sweet, more heat”
                    rendition of the Flagship original.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://truffhotsauce.com/collections/sauce/products/hotter-truff-hot-sauce",
                heat = 4_000
            ),
            HotSauce(
                brandName = "Cholula",
                sauceName = "Original",
                description = """
                    Cholula Original Hot Sauce is created from a generations old recipe that features carefully-selected
                    arbol and piquin peppers and a blend of signature spices. We love it on burgers and chicken but have
                    heard it’s amazing on pizza. Uncap Real Flavor with Cholula Original.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://www.cholula.com/original.html",
                heat = 3_600
            ),
            HotSauce(
                brandName = "Mad Dog",
                sauceName = "357",
                description = """
                   Finally, a super hot sauce that tastes like real chile peppers. This sauce is blended
                   with ingredients that create a sauce fit to take your breath away. About five seconds after you
                   taste the recommended dose of one drop, prepare your mouth and mind for five to 20 minutes of agony
                   that all true chileheads fully understand and appreciate.
                   """.trimIndentsAndRemoveNewlines(),
                url = "https://www.saucemania.com.au/mad-dog-357-hot-sauce-148ml/",
                heat = 357_000
            ),
            HotSauce(
                brandName = "Hot Ones",
                sauceName = "Fiery Chipotle",
                description = """
                    This hot sauce was created with one goal in mind: to get celebrity interviewees on Hot Ones to say
                    "damn that's tasty, and DAMN that's HOT!" and then spill their deepest secrets to host Sean Evans.
                    The tongue tingling flavors of chipotle, pineapple and lime please the palate while the mix of ghost
                    and habanero peppers make this sauce a scorcher. Hot Ones Fiery Chipotle Hot Sauce is a spicy
                    masterpiece.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://chillychiles.com/products/hot-ones-fiery-chipotle-hot-sauce",
                heat = 15_600
            ),
            HotSauce(
                brandName = "Hot Ones",
                sauceName = "The Last Dab",
                description = """
                    More than simple mouth burn, Pepper X singes your soul. Starting with a pleasant burn in the mouth,
                    the heat passes quickly, lulling you into a false confidence. You take another bite, enjoying the
                    mustard and spice flavours. This would be great on jerk chicken, or Indian food! But then, WHAM!
                    All of a sudden your skin goes cold and your stomach goes hot, and you realize the power of X.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://www.saucemania.com.au/hot-ones-the-last-dab-hot-sauce-148ml/",
                heat = 1_000_000
            ),
            HotSauce(
                brandName = "Torchbearer",
                sauceName = "Zombie Apocalypse",
                description = """
                    The Zombie Apocalypse Hot Sauce lives up to its name, combining Ghost Peppers and Habaneros with a
                    mix of spices, vegetables, and vinegar to create a slow burning blow torch. Some people will feel
                    the heat right away, but others can take a few minutes for the full impact to set in. The heat can
                    last up to 20 minutes, creating a perfect match between very high heat and amazing flavor. Try it
                    on all your favorite foods - wings, chili, soups, steak or even a sandwich in need of a major kick.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://heatonist.com/products/zombie-apocalypse",
                heat = 100_000
            ),
            HotSauce(
                brandName = "Heartbeat",
                sauceName = "Pineapple Habanero",
                description = """
                    Pineapple Habanero is Heartbeat Hot Sauce’s most recent offering and their spiciest to date! They’ve
                    yet again collaborated with an Ontario craft brewery, this time from their home town of Thunder Bay.
                    Made with the help of Sleeping Giant Brewery’s award winning Beaver Duck session IPA, this sauce has
                    a boldly pronounced fruitiness and a bright but savoury vibe from start to finish.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://www.saucemania.com.au/heartbeat-pineapple-habanero-hot-sauce-177ml/",
                heat = 12_200
            ),
            HotSauce(
                brandName = "Karma Sauce",
                sauceName = "Burn After Eating",
                description = """
                    Karma Sauce Burn After Eating Hot Sauce is imbued with a unique flavour thanks to green mango,
                    ajwain and hing powder. Forged with a top-secret blend of super hots that may or may not include
                    Bhut Jolokia (Ghost), Scorpion, Carolina Reaper, 7-Pot Brown and 7-Pot Primo. This isn’t a sauce you
                    eat, it’s one you survive.
                    """.trimIndentsAndRemoveNewlines(),
                url = "https://www.saucemania.com.au/karma-sauce-burn-after-eating-hot-sauce-148ml/",
                heat = 669_000
            )
        ))
    }

}

This class has a couple of annotations:

  • DataLoader is annotated with @Component, which marks it so that Spring will autodetect the class when it’s needed. Since the code in this class references the application’s instance of HotSauceRepository, this class will be instantiated when an instance of HotSauceRepository is created.
  • The loadData() method is annotated with @PostConstruct, which specifies that it should be called as soon as the class has been initialized. This annotation is often used to populate databases when a Spring application is launched.

Kotlin Extensions

This class also makes use of a handy Kotlin feature: Extensions. These are properties or functions that can be added to classes to extend their capabilities without having to access their code.

At the beginning of the class, you added an extension to the String class:

fun String.trimIndentsAndRemoveNewlines() = this.trimIndent().replace("\n", " ")

This adds the method trimIndentsAndRemoveNewlines() to the String class, which removes indentations and newline characters from multiline strings. The assignments to each hot sauce’s description property is done using multiline strings (which are delimited with triple-quotes — """) to make the code easier to read.

What’s With the Numbers?

You may have noticed that some of the numbers in the code contain underscore characters, such as on this line of code for the final sauce in the list:

heat = 669_000

Kotlin — like some other languages, including Python, Ruby, and Swift — ignores undescore characters in numbers. This allows you to use them the way we typically use “separator characters” to make numbers easier to read.

For example, in the U.S., we tend to write the number for “one million” as 1,000,000, which is easier to read than 1000000. In India, the preference is to write this number as 10,00,000, and in some countries in Europe, it’s 1.000.000. By allowing the underscore as the grouping character for numbers and ignoring it, Kotlin lets you group digits in numbers as you see fit.

Trying Out the API

You’re now ready to take the API for a trial run!

🌶 Run the application by opening a terminal if you’re on macOS of Linux or opening PowerShell if you’re on Windows), navigating to the the application’s directory, and entering the following command:

./gradlew bootRun

This command was included in the set of files that Spring Initilizr generated. It sets Gradle in motion, causing it to download any needed plugins and dependencies, after which it launches the application.

When it starts, you’ll see a lot of status messages. Eventually, they’ll end with this:

> :bootRun

If you don’t see an error message and the last line of the output is > :bootRun, it means that the project is running properly and listening to localhost:8080 for incoming requests. You can now start making API calls.

Is this thing on?

One of the dependencies that you included when setting up this project with Spring Initializr was Spring Boot Actuator. This automatically adds a number of endpoints to your application that let you monitor and interact with it.

Let’s use Actuator’s health endpoint to get client-side confirmation that your application is up and running.

🌶 If you’re on macOS or Linux, enter this into Terminal:

curl "http://localhost:8080/actuator/health"

🌶 If you’re on Windows, enter this into PowerShell:

Invoke-RestMethod "http://localhost:8080/actuator/health"

macOS and Linux users should see this response...

{"status":"UP"}

...while Windows users should see this:

status
------
UP

The health endpoint is just one of of over 20 endpoints that a Spring Boot application provides when it includes the Actuator dependency. It’s indispensable for managing and monitoring apps during both development and production, as it provides logging, metrics, auditing, HTTP tracing, and process monitoring features, all of which are covered in the Spring documentation.

Count hot sauces

To get the number of hot sauces in the database, send a GET request to the /api/hotsauces/count endpoint.

🌶 If you’re on macOS or Linux, enter this into Terminal:

curl "http://localhost:8080/api/hotsauces/count"

🌶 If you’re on Windows, enter this into PowerShell:

Invoke-RestMethod "http://localhost:8080/api/hotsauces/count"

If you haven’t added or removed any hot sauces since starting the application, the API should report that there are 9.

List hot sauces

To get the complete list of hot sauces, send a GET request to the /api/hotsauces endpoint.

🌶 If you’re on macOS or Linux, enter this into Terminal:

curl "http://localhost:8080/api/hotsauces"

🌶 If you’re on Windows, enter this into PowerShell:

Invoke-RestMethod "http://localhost:8080/api/hotsauces"

macOS and Linux users will be presented with a JSON array of dictionaries, with each dictionary representing a hot sauce. Windows users will see a nicely formatted list of hot sauces and their properties.

Filtering hot sauces with parameters

Try using the optional parameters to limit the results. In the example below, the maxheat parameter is being used to limit the response to only those hot sauces with a Scoville rating of 10,000 or less.

🌶 If you’re on macOS or Linux, enter this into Terminal:

curl "http://localhost:8080/api/hotsauces?maxheat=10000"

🌶 If you’re on Windows, enter this into PowerShell:

Invoke-RestMethod "http://localhost:8080/api/hotsauces?maxheat=10000"

If you haven’t added or removed any hot sauces since starting the application, the API should return three hot sauces: Truff Hot Sauce, Truff Hotter Sauce, and Cholula.

Add a new hot sauce

Add a new sauce to the database by sending a POST request to /api/hotsauces/, along with the attributes of the new sauce — except id, which will automatically be assigned to the new sauce. The attributes should be in JSON dictionary form.

🌶 If you’re on macOS or Linux, enter this command into the terminal:

curl --request POST \
    --url http://localhost:8080/api/hotsauces/ \
    -H "Content-Type: application/json" \
    --data '{"brandName": "Dave’s Gourmet", "sauceName": "Temporary Insanity", "url": "https://store.davesgourmet.com/ProductDetails.asp?ProductCode=DATE", "description": "This sauce has all the flavor of Dave’s Original Insanity with less heat. Finally, there’s sauce for when you only want to get a little crazy in the kitchen. Add to stews, burgers, burritos, and pizza, or any food that needs an insane boost. As with all super hot sauces, this sauce is best enjoyed one drop at a time!", "heat": 57000}'

🌶 If you’re on Windows, enter this command into PowerShell:

$body = @{
    brandName = "Dave\'s Gourmet"
    sauceName = "Temporary Insanity"
    url = "https://store.davesgourmet.com/ProductDetails.asp?ProductCode=DATE"
    description = "This sauce has all the flavor of Dave\'s Original Insanity with less heat. Finally, there's sauce for when you only want to get a little crazy in the kitchen. Add to stews, burgers, burritos, and pizza, or any food that needs an insane boost. As with all super hot sauces, this sauce is best enjoyed one drop at a time!"
    heat = 57000
} | ConvertTo-Json
Invoke-RestMethod "http://localhost:8080/api/hotsauces/" `
    -Method POST `
    -ContentType "application/json" `
    -Body $body

The hot sauce will be added to the database, and the API will respond with information about the newly added sauce.

You can confirm that the hot sauce has been added to the database by requesting the complete list, or by requesting it by id.

Edit a hot sauce

Make changes to a hot sauce in the database by sending a PUT request to /api/hotsauces/{id} where {id} is the hot sauce’s id value, along with the attributes you wish to change in JSON dictionary form.

🌶 If you’re on macOS or Linux, enter this command into the terminal:

curl --request PUT \
    --url http://localhost:8080/api/hotsauces/10 \
    -H "Content-Type: application/json" \
    --data '{"brandName": "NewCo", "sauceName": "Generic Hot Sauce", "description": "It’s hot. It’s sauce. That’s it.", "heat": 1000}'

🌶 If you’re on Windows, enter this command into PowerShell:

$body = @{
    brandName = "NewCo"
    sauceName = "Generic Hot Sauce"
    url = "https://store.davesgourmet.com/ProductDetails.asp?ProductCode=DATE"
    description = "It's hot. It's sauce. That's it."
    heat = 1000
} | ConvertTo-Json
Invoke-RestMethod "http://localhost:8080/api/hotsauces/10" `
    -Method PUT `
    -ContentType "application/json" `
    -Body $body

The hot sauce will be updated with your changes and the API will respond with information showing the changes you made.

Delete a hot sauce

Delete a hot sauce by sending a DELETE request to /api/hotsauces/{id} where {id} is the hot sauce’s id value.

🌶 If you’re on macOS or Linux, enter this command into Terminal:

curl --request DELETE \
    --url http://localhost:8080/api/hotsauces/10

🌶 If you’re on Windows, enter this command into PowerShell:

Invoke-RestMethod "http://localhost:8080/api/hotsauces/10" `
    -Method DELETE

You can confirm that the hot sauce has been removed from the database by requesting the complete list (using curl http://localhost:8080/api/hotsauces/) or a count of hot sauces (using curl http://localhost:8080/api/hotsauces/count).

Securing the API

Right now, the entire API is unsecured. All its endpoints are available to anyone with the URL and the ability to send HTTP requests.

Suppose that we want to allow only authorized applications to have access to the endpoints of the API that allow accessing, adding, editing, and deleting hot sauces. The remaining endpoints — the test endpoint and the one that reports the number of hot sauces — can remain generally available.

You’ll use Auth0 to secure the API’s CRUD endpoints, but before that happens, let’s take a look at how API authentication works.

The Nightclub Metaphor

There’s no shortage of essays and articles on the internet that try to explain API authentication. Let’s make it a little more fun and easier to grasp way by using The Nightclub Metaphor!

A number of nightclubs have a two-step process to control who gets in and who doesn’t. First, there’s an initial phase where the club-goer goes to a window and shows some ID...

A fake ID

...and if the ID passes inspection, the club-goer is given something that’s easy to see in a nightclub environment. Often, it’s a wristband:

Club wristbands

The wristband is visible permission — an access token — that the club-goer wearing it has been cleared to enter the club. In order to be let into the club, the club-goer has to show their wristband to the bouncers guarding the entrance.

The better-run nightclubs change their wristbands every night. One night, it’ll be blue, and on the next night, it’ll be a different color. This prevents club-goers from reusing the previous night’s wristband to get in.

API security with Auth0 works in a similar way:

  • Showing ID at the window: You first assemble a set of credentials, which you submit to Auth0’s servers to obtain an access token.
  • Showing your wristband to the bouncers: When accessing an API endpoint that requires authorization, you include the access token as part of your request. The API communicates with Auth0 to validate the access token, and if it’s valid, provides the response to your request.
  • Changing wristbands every night: Like the wristbands at the better-run nightclubs, access tokens aren’t valid forever. By default, Auth0 access tokens expire 24 hours after they’re issued.

It’s time to secure the API. This needs to be done both on the Auth0 side as well as in the application.

Setting Up API Authentication on the Auth0 Side

Registering the API

The first step in setting up API authentication is to register it.

🌶 Log into Auth0.

If you don’t have an Auth0 account yet, go ahead and sign up for a free one. You’ll find it useful for prototyping logins and API security in your projects.

🌶 Once you’ve logged in, navigate to the APIs page...

Auth0 dashboard APIs page

🌶 ...and click the Create API button located near the upper right corner of the page. A form will appear, asking for basic information about the API:

New API pop-up form

🌶 Fill it out like so:

🌶 When you’re done providing this information, click the Create button. The API will now be registered, which means that Auth0 can now be used to authorize access to it.

Getting the Credentials

The next step is to gather the necessary Auth0 credentials that will be needed to request an access token from Auth0 and to validate any access tokens.

Immediately after registering your API, you were taken to the API’s newly-created Quick Start page, pictured below:

Auth0 Quick Start page for the API

This is technically a machine-to-machine application, which means that the API you created (a machine) wouldn’t normally be accessed directly by a human, but by an application (another machine). You’ll need to gather the credentials from the Machine to Machine Applications section.

🌶 Click on Machine to Machine Applications, which will take you to this page:

Auth0 Settings page

This is the first of two pages that provide information that you need in order to get the access token.

🌶 You’ll be copying four items, and you may find it helpful to open a text editor so that you have some place in which to paste them.

🌶 Copy the API Identifier value, which is located just below the page’s headline...

Identifier

🌶 ...and paste it into the text editor.

Your text editor should look something like this:

API Identifier: {API Identifier}

If you’ve been following the steps in this exercise, {API Identifier} will be http://hotsauces.demo.

There will be a list of applications at the bottom of the page. One of them will be HotSauces. Click on its name. You’ll be taken to HotSauces’ Machine to Machine → Settings page, which contains information that you’ll need to get the token:

Auth0 Machine to Machine page

🌶 Use the “copy” buttons on the right side of these fields to copy and paste their contents into the same text editor where you pasted the API Identifier:

  • Domain
  • Client ID
  • Client Secret

Your text editor should now look something like this:

API Identifier: {API Identifier}
Domain: {Domain}
Client ID: {Client ID}
Client Secret: {Client Secret}

You’ve done all the necessary setup on the Auth0 side. It’s now time to do the same on the application side.

Setting Up API Authentication on the Application Side

Adding Security Dependencies

The first step is to add the necessary security dependencies to the Gradle build file.

🌶 Update the dependencies block in /build.gradle.kts so that it looks like this:

// /build.gradle.kts (excerpt)

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    runtimeOnly("com.h2database:h2")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("org.springframework.security:spring-security-oauth2-resource-server")
    implementation("org.springframework.security:spring-security-oauth2-jose")
    implementation("org.springframework.security:spring-security-config")
}

This adds four dependencies. Note that they all have “security” in their name and that two of them also include “oauth2”, a reference to the OAuth2 protocol that Auth0 uses for authorization.

The first time you run the application after adding these lines to the Gradle build file, Gradle will download and install these dependencies.

Adding a Configuration File

The next step is to create a file that configures the application to use the correct API identifier and domain when authenticating with Auth0.

🌶 Create a new file named application.yml in the /src/main/resources/ directory. It should contain the following:

# /src/main/resources/application.yml

auth0:
  audience: { Domain }
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://{API Identifier}/

Indentation is important in this file: Don’t remove any indents, and use space characters to make the indents. Do NOT use tab characters!

🌶 In the file shown above, replace {API Identifier} and {Domain} with the values that you copied into your text editor. If you’ve been following the steps in this exercise, {API Identifier} will be http://hotsauces.demo.

The {API Identifier} value will be used to tell Auth0 which API is asking for authentication, and the {Domain} value allows Spring’s security to get the authorization server’s public keys and validate the access token.

Adding Security Classes

The final step in securing the API is adding a couple of security classes. These classes enable the application to make use of Auth0’s OAuth2 authorization.

Since these classes don’t directly have anything to do with the application’s main functionality, they’re often put into their own directory.

🌶 Create a new directory, security, as a subdirectory of /src/main/kotlin/com/auth0/hotsauces/. The project’s directory structure should now look like this:

Project structure, pointing out the security folder and application.yml

The first security class that you’ll add is an audience validator. It confirms that the access token is actually the one for the API.

🌶 Create a new file named AudienceValidator.kt in the /src/main/kotlin/com/auth0/hotsauces/security/ directory:

// /src/main/kotlin/com/auth0/hotsauces/security/AudienceValidator.kt

package com.auth0.hotsauces.security

import org.springframework.security.oauth2.core.OAuth2Error
import org.springframework.security.oauth2.core.OAuth2TokenValidator
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult
import org.springframework.security.oauth2.jwt.Jwt

class AudienceValidator(private val audience: String) : OAuth2TokenValidator<Jwt> {

    override fun validate(jwt: Jwt): OAuth2TokenValidatorResult {
        val error = OAuth2Error("invalid_token", "The required audience is missing", null)
        return if (jwt.audience.contains(audience)) {
            OAuth2TokenValidatorResult.success()
        } else OAuth2TokenValidatorResult.failure(error)
    }

}

The second security class configures API security in two ways:

  • It validates the access token, and
  • It specifies the levels of access you grant to the API endpoints.

🌶 Create a new file named SecurityConfig.kt in the /src/main/kotlin/com/auth0/hotsauces/security/ directory:

// /src/main/kotlin/com/auth0/hotsauces/security/SecurityConfig.kt

package com.auth0.hotsauces.security

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator
import org.springframework.security.oauth2.core.OAuth2TokenValidator
import org.springframework.security.oauth2.jwt.*

@EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {

    @Value("\${auth0.audience}")
    private val audience: String = String()

    @Value("\${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
    private val issuer: String = String()

    @Bean
    fun jwtDecoder(): JwtDecoder {
        val jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer) as NimbusJwtDecoder
        val audienceValidator: OAuth2TokenValidator<Jwt> = AudienceValidator(audience)
        val withIssuer: OAuth2TokenValidator<Jwt> = JwtValidators.createDefaultWithIssuer(issuer)
        val withAudience: OAuth2TokenValidator<Jwt> = DelegatingOAuth2TokenValidator(withIssuer, audienceValidator)
        jwtDecoder.setJwtValidator(withAudience)
        return jwtDecoder
    }

    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.authorizeRequests()
            .mvcMatchers("/api/hotsauces/count").permitAll()
            .mvcMatchers("/api/hotsauces").authenticated()
            .mvcMatchers("/api/hotsauces/*").authenticated()
            .and()
            .oauth2ResourceServer().jwt()
    }

}

A closer look at the authorizations you just defined

Take a closer look at the configure() method you just entered:

@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
    http.authorizeRequests()
        .mvcMatchers("/api/hotsauces/count").permitAll()
        .mvcMatchers("/api/hotsauces").authenticated()
        .mvcMatchers("/api/hotsauces/*").authenticated()
        .and()
        .oauth2ResourceServer().jwt()
}

These lines specify that the /api/hotsauces/count endpoints will be accessible to everyone...

.mvcMatchers("/api/hotsauces/count").permitAll()

...while these specify that the /api/hotsauces endpoint and any other endpoint beginning with /api/hotsauces/ require authentication.

.mvcMatchers("/api/hotsauces").authenticated()
.mvcMatchers("/api/hotsauces/*").authenticated()

In other words, any request to these endpoints must include a valid access token in order to work. Otherwise, those requests will get an HTTP 401 — Unauthorized — response.

Here’s what the project’s final directory structure looks like:

Project file structure, showing the newly-added AudienceValidator and SecurityConfig files

Trying Out the Secured API

Now that you’ve secured the API, it’s time to try it out!

Acquiring an access token

You now have the necessary pieces of information needed to request a token. You’ll request the token by sending a POST request containing the information.

You might find it easier to assemble the POST request in the same text editor where you pasted the information.

🌶 If you’re on macOS or Linux, start with this cURL command, replacing the {Domain}, {Client ID}, {Client Secret}, and {API Identifier} with the corresponding values you copied.

curl --request POST \
    --url https://{Domain}/oauth/token \
    --header 'content-type: application/json' \
    --data '{
        "client_id": "{Client ID}",
        "client_secret": "{Client Secret}",
        "audience": "{API Identifier}",
        "grant_type": "client_credentials"
    }'

🌶 If you’re on Windows, use this command instead. As with the macOS/Linux version, replace the {Domain}, {Client ID}, {Client Secret}, and {API Identifier} with the corresponding values you copied.

$Body = @{
    "client_id" = "{Client ID}"
    "client_secret" = "{Client Secret}"
    "audience" = "{API Identifier}"
    "grant_type" = "client_credentials"
}
$response = Invoke-RestMethod "https://{Domain}/oauth/token" `
    -Method POST `
    -Body $Body
$response | ConvertTo-Json

In response, you should receive a JSON dictionary that looks like this:

{
  "access_token": "{Access Token (a 720-character string)}",
  "expires_in": 86400,
  "token_type": "Bearer"
}

Note that one of the dictionary keys is expires_in, which specifies that the token will expire in 86,400 seconds, or in more convenient units, 24 hours. After that time has elapsed, the token will be invalid and you’ll have to request a new one following the same steps above.

Copy the {Access token} value and paste it into the text editor with the other values.

Your text editor should now look something like this:

API Identifier: {API Identifier}
Domain: {Domain}
Client ID: {Client ID}
Client Secret: {Client Secret}

Access Token: {Access Token}

Trying the Public Endpoints

🌶 Open a terminal or PowerShell window, navigate to the project directory, and enter ./gradlew bootRun.

Start with the endpoint that remained public: The one that returns the number of hot sauces in the database.

🌶 If you’re on macOS or Linux, enter this command into Terminal:

curl -i http://localhost:8080/api/hotsauces/count

The -i option tells cURL to include the HTTP response headers in its output. By using it, you can see the HTTP status code for the API’s reponse.

The response should still be the number of hot sauces. It will be preceded by the response header, which should look like this:

HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Frame-Options: DENY
Content-Type: application/json
Transfer-Encoding: chunked
Date: {Date and time when the response was issued}

The status code is the number at the end of the header’s first line: 200, or “OK”.

🌶 If you’re on Windows, enter this command into PowerShell:

Invoke-WebRequest "http://localhost:8080/api/hotsauces/count"

You're using PowerShell's Invoke-WebRequest this time, which means that the response will look like this:

StatusCode        : 200
StatusDescription :
Content           : 9
RawContent        : HTTP/1.1 200
                    Transfer-Encoding: chunked
                    Keep-Alive: timeout=60
                    Connection: keep-alive
                    Content-Type: application/json
                    Date: Fri, 23 Jul 2021 14:05:52 GMT

                    9
Forms             : {}
Headers           : {[Transfer-Encoding, chunked], [Keep-Alive, timeout=60], [Connection, keep-alive], [Content-Type,
                    application/json]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : System.__ComObject
RawContentLength  : 1

The number of sauces is the value in the Content line, and as with the macOS/Linux version, the status code is the number at the end of the header’s first line: 200, or “OK”.

Trying the Protected Endpoints

It’s time to try the endpoints that require authorization. Before using the token, try requesting a list of all the hot sauces without it.

🌶 If you’re on macOS or Linux, enter this command into Terminal:

curl -i http://localhost:8080/api/hotsauces/

Instead of a JSON list of dictionaries of hot sauces, you’ll get an empty reply. The first line of the header should be your hint as to why:

HTTP/1.1 401

🌶 If you’re on Windows, enter this command into PowerShell:

Invoke-WebRequest "http://localhost:8080/api/hotsauces/"

You'll see this reply:

Invoke-WebRequest : The remote server returned an error: (401) Unauthorized.
At line:1 char:1
+ Invoke-WebRequest "http://localhost:8080/api/hotsauces/"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExc
   eption
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

The 401 status code means “Unauthorized”. To refer back to the nightclub metaphor, you’re trying to get into the club without a wristband.

Getting a list of all the hot sauces from the secured API

Make the request again, this time including the access token.

🌶 If you’re on macOS or Linux, enter this command into the terminal, making sure to replace {Access Token} with the value that you copied into your text editor:

curl -i --request GET \
     --url http://localhost:8080/api/hotsauces/ \
     -H "Content-Type: application/json" \
     -H "authorization: Bearer {Access Token}"

🌶 If you’re on Windows, enter this command into PowerShell, making sure to replace {Access Token} with the value that you copied into your text editor:

$accessToken = "{Access Token}"
$headers = @{
    Authorization = "Bearer $accessToken"
}
$response = Invoke-RestMethod "http://localhost:8080/api/hotsauces/" `
    -Headers $headers
$response | ConvertTo-Json

This time, when you make the request, you’ll get the list of all the hot sauces.

Adding a hot sauce using the secured API

Try adding add a hot sauce using the access token.

🌶 If you’re on macOS or Linux, enter this command into the terminal:

curl -i --request POST \
    --url http://localhost:8080/api/hotsauces/ \
    -H "Content-Type: application/json" \
    --data '{"brandName": "Dave’s Gourmet", "sauceName": "Temporary Insanity", "url": "https://store.davesgourmet.com/ProductDetails.asp?ProductCode=DATE", "description": "This sauce has all the flavor of Dave’s Original Insanity with less heat. Finally, there’s sauce for when you only want to get a little crazy in the kitchen. Add to stews, burgers, burritos, and pizza, or any food that needs an insane boost. As with all super hot sauces, this sauce is best enjoyed one drop at a time!", "heat": 57000}' \
    -H "authorization: Bearer {Access Token}"

🌶 If you’re on Windows, enter this command into PowerShell:

$body = @{
    brandName = "Dave's Gourmet"
    sauceName = "Temporary Insanity"
    url = "https://store.davesgourmet.com/ProductDetails.asp?ProductCode=DATE"
    description = "This sauce has all the flavor of Dave\'s Original Insanity with less heat. Finally, there's sauce for when you only want to get a little crazy in the kitchen. Add to stews, burgers, burritos, and pizza, or any food that needs an insane boost. As with all super hot sauces, this sauce is best enjoyed one drop at a time!"
    heat = 57000
} | ConvertTo-Json
$response = Invoke-RestMethod "http://localhost:8080/api/hotsauces/" `
    -Method POST `
    -ContentType "application/json" `
    -Headers $headers `
    -Body $body
$response | ConvertTo-Json

Editing a hot sauce using the secured API

Try editing the hot sauce with id 10.

🌶 If you’re on macOS or Linux, enter this command into the terminal:

curl -i --request PUT \
    --url http://localhost:8080/api/hotsauces/10 \
    -H "Content-Type: application/json" \
    --data '{"brandName": "NewCo", "sauceName": "Generic Hot Sauce", "description": "It’s hot. It’s sauce. That’s it.", "heat": 1000}' \
    -H "authorization: Bearer {Access Token}"

🌶 If you’re on Windows, enter this command into PowerShell:

$body = @{
    brandName = "NewCo"
    sauceName = "Generic Hot Sauce"
    description = "It’s hot. It’s sauce. That’s it."
    heat = 1000
} | ConvertTo-Json
$response = Invoke-RestMethod "http://localhost:8080/api/hotsauces/10" `
    -Method PUT `
    -ContentType "application/json" `
    -Headers $headers `
    -Body $body
$response | ConvertTo-Json

Note that since you didn’t change the url property of the sauce, it remains the same.

Deleting a hot sauce using the secured API

Then try deleting the hot sauce with id 10.

🌶 If you’re on macOS or Linux, enter this command into the terminal:

curl -i --request DELETE \
    --ur l http://localhost:8080/api/hotsauces/10
    -H "authorization: Bearer {Access Token}"

🌶 If you’re on Windows, enter this command into PowerShell:

Invoke-RestMethod "http://localhost:8080/api/hotsauces/10" `
    -Method DELETE `
    -Headers $headers

List the hot sauces or get a count to confirm that the sauce is no longer in the list.

If you’ve made it this far, congratulations! You’ve just developed and secured an API with Spring Boot, Kotlin, and Auth0!

  • Twitter icon
  • LinkedIn icon
  • Faceboook icon