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, 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 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 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
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
andInvoke-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:
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
, entercom.abc123
. If you don’t have a domain name, just entercom.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.
- 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
- 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:
🌶 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
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 theid
field in the corresponding database table. - The
@Lob
annotation is short for “large object”. It’s being used to annotate thedescription
andurl
properties because they could contain strings longer than 256 characters. By default, JPA mapsString
s in entities to theVARCHAR(256)
type in the database; marking aString
as@Lob
tells JPA to map it to theTEXT
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’sid
value shouldn’t be changed after it’s been created, so we’re declaring it withval
.var
is short for “variable” and that’s what it declares. All the other class properties should be editable, so we’re declaring them withvar
.
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:
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})
— Returnstrue
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 whosebrandName
contains the given string.saucename
Limits the results to only those sauces whosesauceName
contains the given string.desc
Limits the results to only those sauces whosedescription
contains the given string.minheat
Limits the results to only those sauces whoseheat
rating is greater than or equal to the given number.maxheat
Limits the results to only those sauces whoseheat
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 withapi/hotsauces
.- Any method annotated with
@GetMapping()
responds toGET
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 toPOST
,PUT
, andDELETE
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 ofHotSauceRepository
, this class will be instantiated when an instance ofHotSauceRepository
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...
...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:
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...
🌶 ...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:
🌶 Fill it out like so:
- Name: Enter
HotSauces
. - Identifier: Enter
http://hotsauces.demo
. - Signing Algorithm: Select RS256.
🌶 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:
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:
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...
🌶 ...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:
🌶 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:
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:
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 i
nclude 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!
AMPValidationMessage