DEV Community 👩‍💻👨‍💻

Cover image for Designing a secure API
Pedro Aravena for Vaultree

Posted on • Updated on

Designing a secure API

Designing a secure API: Best Practices

Application programming interface or API in general is a bridge that connects two or more programs and it is through this bridge that it is possible to make companies interact, for example, through payment. It is a very generic term that applies from public methods of a framework to integration with microservices. API is one of the terms that has been widely discussed lately, but what are the minimum requirements to create or maintain one? What are the trade-offs, strategies and, at the end, what makes an API secure?
In this article, we'll outline ten steps for creating and maintaining a safe API.

Image description

I API Design

First, let's talk about the types of contracts that exist within an API - it's important to highlight that in this article we'll discuss APIs through Web services. So, as the first item, it is crucial to talk about the beginning of an application's life cycle. An important point within an API is that it must establish pre-established behaviours through a Spec, contract or simply convention. Considering the creation from this contract there are two ways to start an API.

Contract First

Contract first, as the name implies, is when the construction of the API starts with the contract or conditions of the API operation. As with any software engineering strategy, there are trade-offs. The biggest advantage of this approach is that once the API is fully specified, it is possible to parallelize the work, for example, while the mobile and front-end team mock the services and the back-end team manages to implement the business. However, this approach has some disadvantages. Initially, the great effort to close the scope of the API makes several teams wait in the meantime. Moreover, as most teams rely on agile operations and ever-changing software, closing the scope of the API can be a difficult and time-consuming task.

Contract Last

Contract Last is the opposite of contract first. That is, it starts with the code and then the API is created. The biggest advantage of this approach, of course, is the possibility of reusing existing classes and methods. However, maintaining compatibility between the existing code and the service is one of the biggest challenges.

ⅠⅠ Glory of Rest

Presented by Leonard Richardson at QCon in 2008, it is also known as the Richardson Maturity Model or simply Richardson Maturity Model. Richardson proposes 4 levels of maturity for a good design of a Rest API over the HTTP Protocol. The Glory of Rest is when you reach the maximum level, that is, level 3, as a sign that you are using REST properly in your API. These levels are divided:

Level 0 — The Swamp of POX (Plain Old XML)

It's what Richardson called a URI and an HTTP method/verb. But after all, what does that mean? Level 0, despite using HTTP as a transport system for remote interactions, does not rely on multiple URIs or HTTP methods/verbs or the HATEOAS capability, which we have at higher levels. It's the most basic level of maturity, and it doesn't use any fancy features other than HTTP using a tunnelling mechanism like remoting. But note that it does not necessarily mean that you are using REST by using HTTP.

We have as an example SOA, in its most primitive form, which uses the POST verb and uses an XML structure as the body of communication between services. This means that all the verbs defined by Roy Fielding in his thesis are not used. That is, all functions are accessed by sending a POST request to a single URI. This way, it reminds us of what many may not remember, but services based on XML-RPC sending data as POX — Plain Old XML.

We can assume that I want to make an appointment at a barbershop with a specific barber. We have an application where you seek to check availability at times of a certain date for a specific doctor.

POST /serviceSchedulingBarber
{
“schedulingRequest”:{
“date”:”2020–08–19",
“barber”: “john”
}
}
Enter fullscreen mode Exit fullscreen mode

When you submit this request with POST, we will get a return as follows:

HTTP/1.1 200 OK
{
“listAvailableTimes”:[
{
“start”:”2pm",
“end”:”3pm",
“barber”:”john”
},
{
“start”:”4pm",
“end”:”5pm",
“barber”:”john”
}
]
}
Enter fullscreen mode Exit fullscreen mode

From the moment you end up choosing which time you want to schedule, you must send a new request using the same HTTP verb and the same URI.

POST /serviceSchedulingBarber
{
“schedulingRequest”:{
“start”:”2pm",
“end”:”3pm",
“barber”:”john”,
“client”:”peter”
}
}
Enter fullscreen mode Exit fullscreen mode

Assuming that if the appointment is confirmed, you should receive a confirmation like the answer below.

HTTP/1.1 200 OK
{
“scheduling”:{
“start”:”2pm",
“end”:”3pm",
“barber”:”john”,
“client”:”peter”
}
}
Enter fullscreen mode Exit fullscreen mode

In case someone ends up scheduling before you and you can't see it, notice that the message below, despite the HTTP status being 200,shows that the transaction was not completed.

HTTP/1.1 200 OK
{
"FailRequireSchedule":{
“start”:”2pm",
“end”:”3pm",
“barber”: “john”,
“client”:”peter”,
“reason”:”Schedule is no longer available”
}
}
Enter fullscreen mode Exit fullscreen mode

As you can see it is a very basic way of making an API. Let's move on to the next level of maturity.

Level 1 — Individual Resources

This is what we call a URI based on multiple resources and an HTTP method/verb. It's when your range of URI's starts to increase and become more diverse, but your HTTP verb is still unique. When you have a feature like that in your API, we can say that you are at maturity level 1. In terms of modern APIs, level 1 is not the ideal scenario, but it's better than level 0. As a REST API, although getting closer, with multiple more individual endpoints for certain purposes.

Taking the barbershop as an example, let's make an appointment with the catatau barber.

POST /barber/catatau HTTP/1.1
{
“schedulingRequest”:{
“date”:”2020–08–19"
}
}
Enter fullscreen mode Exit fullscreen mode

In response to the above request we have:

HTTP/1.1 200 OK
{
“listAvailableTimes”:[
{
“id”:”1234",
“start”:”2pm",
“end”:”3pm",
“barber”: “john”
},
{
“id”:”1235",
“start”:”4pm",
“end”:”5pm",
“barber”: “john”
}
]
}

Enter fullscreen mode Exit fullscreen mode

In the navigability flow, you must choose a time and submit a different URI as shown below:

POST /time/1234 HTTP/1.1
{
“schedulingRequest”:{
“client”:”peter”
}
}
Enter fullscreen mode Exit fullscreen mode

If the query is confirmed we will have something like that.

HTTP/1.1 200 OK
{
“schedule”:{
“start”:”2pm",
“end”:”3pm",
“barber”: “John”,
“client”:”Peter”
}
}

Enter fullscreen mode Exit fullscreen mode

Notice that we no longer have just one resource, but different URIs for each action.

Level 2 — HTTP Verbs

We've reached a more complete maturity level, where we're starting to not only have individual resources like multiple URIs but also multiple HTTP. Here, we have reached a level of maturity that we call REST. We have verbs for each action within CRUD (Create-Read-Update-Delete). In previous maturity models, the only verb used, even for querying, was POST.

Let's continue with the example, notice that to check an available date for scheduling, we stopped using POST and started using GET.

GET /barber/john/schedule?date=20200819 status=open

In this case we will receive a JSON with the format below.

HTTP/1.1 200 OK
{
“listAvailableTimes”:[
{
“id”:”1234",
“start”:”2pm",
“end”:”3pm",
“barber”: “john”
},
{
“id”:”1235",
“start”:”4pm",
“end”:”5pm",
“barber”: “john”
}
]
}
Enter fullscreen mode Exit fullscreen mode

As REST defines itself, for queries or selects in the Database, we must use the HTTP GET verb. This is very important, because we start to have a pattern in the request for available features. To move on with the next step to book an appointment at the barbershop, you must use the POST, as you will enter new information in the database.

POST /time/1234 HTTP/1.1
{
“schedulingRequest”:{
“client”:”Peter”
}
}
Enter fullscreen mode Exit fullscreen mode

When making the request, the HTTP Status of the HTTP response is no longer 200 OK, we get a 201 Created.

HTTP/1.1 201 Created
{
“Scheduling”:{
“id”:”1234",
“start”:”2pm",
“end”:”3pm",
“barber”: “john”,
“client”:”peter”
}
}
Enter fullscreen mode Exit fullscreen mode

In case of failure, we would have a different HTTP Status as an exception will be thrown. This indicates that the request, with HTTP Status different from 201, was not successfully performed and that probably, someone a little earlier managed to schedule an appointment at the barbershop. We must respect REST here, where we introduce HTTP verbs and HTTP response codes in this maturity model.

HTTP/1.1 409 Conflict
{
“ListAvailableTimes”:[
{
“id”:”1235",
“start”:”4pm",
“end”:”5pm",
“barber”: “john”
}
]
}
Enter fullscreen mode Exit fullscreen mode

Level 3 — Hypermedia Controls

The top of the maturity chain comes with a not very pretty and difficult to pronounce word, HATEOAS (Hypertext As The Engine Of Application State). The result, though, it's beautiful. This level provides a navigation resource, so that the entire navigation flow ends up being documented in the metadata itself.

We continue with the example of Barbershop.

GET /barber/John/schedule?date=20200819&status=open

In this case we will receive a JSON with the format below.

HTTP/1.1 200 OK
{
“listAvailableTimes”:[
{
“id”:”1234",
“start”:”2pm",
“end”:”3pm",
“barber”: “john”,
“links”:[
{
“href”:”/time/1234",
“rel”:”schedule”,
“type”:”POST”
}
]
},
{
“id”:”1235",
“start”:”4pm",
“end”:”5pm",
“barber”: “john”,
“links”:[
{
“href”:”/time/1235",
“rel”:”scheduling”,
“type”:”POST”
}
]
}
]
}
Enter fullscreen mode Exit fullscreen mode

Note that each available time has a link that contains a URI to show us how to schedule an appointment and what the next step in the flow is.

POST /time/1234 HTTP/1.1
{
“schedulingRequest”:{
“client”:”peter”
}
}

Enter fullscreen mode Exit fullscreen mode

And the scheduling response returns us various hypermedia controls, or simply streams of different things that we can do next. As in the example below that returns a URI of how to cancel the appointment.

{
“Scheduling”:{
“id”:”1234",
“start”:”2pm",
“end”:”3pm",
“barber”: “john”,
“client”:”peter”,
“links”:[
{
“href”:”/time/1235/cancel”,
“rel”:”cancelSchedule”,
“type”:”DELETE”
}
]
}
}
Enter fullscreen mode Exit fullscreen mode

Level 3, the top level of REST maturity, is the absolute Glory of Rest. We can use a lot of HTTP resources that Roy Fielding ended up suggesting in his project.

Ⅲ Documentation

Documentation is everything that the engineering team writes. It is, however, kind of ignored by other developers. And there are several reasons for this, for example, poorly documented or outdated information. The good news is that there are good tools that can help us on this documentation journey. Swagger, for example, greatly simplifies this process for us. There is also the Open API whose objective is to create a specification for the APIs, bringing several advantages in addition to the documentation. With a little help, it is possible to generate an SDK for several languages ​​from the Open API. For example, if you are from the Java world you can add code generation with a maven plugin. And it is possible to generate this SDK for several languages. In addition to automatically generating the APIs SDK, there is the option to create manually. Despite the work, there is the possibility of polishing this SDK with, for example, DSL and Fluent APIs concepts. In this approach it is worth using the language documentation if you use Java or JavaDoc, for example.

ⅠⅤ Versioning

Image description

Let's talk about versioning. But don't think it's git versioning. Let's talk about how to version your API so that what's ongoing in your application doesn't break when you need to make a change. Think about the impact to your already integrated clients if you have to change anything in the API. Here's a list of 4 important ways to version our APIs:

URI versioning

One way or another, this is the most pragmatic and straightforward way of versioning an API despite the fact that it violates a principle that a URI must refer to a single, unique resource. In this case we have an example below on how we could version.

http://yourapi.domain.com/api/v1/doSomething

In this example, it is clear that the version is part of the API content and cannot be just numeric. It should follow a pattern using the version v and x which would be the version number like “vx”. There are those who say that this is not the best way, but as I always said, for every choice a resignation. The advantage of versioning the API is that it allows freedom to change and evolve the code without impacting the legacy.

http://yourapi.domain.com/api/doSomething

http://yourapi.domain.com/api/v2/doSomething

Another interesting thing is an alias of the main URI always to the latest version of the URI. That is, if the most current version of your API is 2, then the alias must be pointing to that version. In the case of an upgrade from version 1 to 2, you must tell clients to change only the URI as the main URI would be for the current version. If the intention is to make this API obsolete, it is necessary to discontinue it, so with this way everything is much more pragmatic to be discontinued.

Query Parameters

Another way to version and which is not very cool because you end up not following Rest to the letter is with Query Parameters. You create a parameter and pass the version as an argument, in case you are using an HTTP POST Verb, using this model doesn't make much sense.

http://yourapi.domain.com/api/doSomething?version=1

Customising Request-Header

REST APIs can use this other way of versioning with custom Headers and the version number being used as an attribute of the Header. Your advantage is that it always preserves the original URI

curl -H “Accepts-version: 1.0” http://yourapi.domain.com/api/doSomething

Versioning the Accept Header

Although this model also leaves the versioning URL free, it has a certain degree of complexity in managing versions and ends up leaving the responsibility for the API Controllers. In the end we have a super complex API, and with that our clients need to know which headers to send before requesting a request.

Accept: application/vnd.example.v1+json

Accept: application/vnd.example+json;version=1.0

Ⅴ Area Code

It is very difficult to talk about API and services without talking about DDD. There are several reasons for this. One of them is the concept of ubiquitous language that aims to bring code closer to business, which is very important when creating an API. In addition to good naming practices, it is extremely vital to separate the domains and subdomains of the application. The concept of subdomain is quite interesting as it aims to divide the company's business for better understanding.
Once the concept of division has been mentioned, there is a problem in defining boundaries and services. It takes a good balance and knowledge on when and how to break boundaries. There are several cases where breakage has brought advantages, however, there's an equal or greater number where APIs have returned to monoliths.

ⅤⅠ Clean Architecture

Let's talk about clean architecture with a focus on layered architecture. It simply boils down to the division of 4 large layers: presentation, application, domain and infrastructure.

Image description

  • We can say that the presentation layer contains all the classes responsible for sending the response back to the client, that is, where our Controllers are located.
  • The application layer contains business logic that is not part of the business rules, such as an orchestration service.
  • The domain layer, as the name implies, represents the domain of your application, consisting of entities (Entity classes), some services (Service classes), all the business rules must be here. If it's a business rule and it's not here, it's wrong.
  • The infrastructure layer, also known as the persistence layer, contains all the classes that perform some strictly technical function. For example, our repositories and our configuration classes. Its benefits go beyond simplicity, with consistency even in different projects, separation of responsibilities and the ease of knowing where to change something when necessary.

Ⅶ Database

This item is quite fast, regarding the database it is not based on the choice of database, whether relational or non-relational. Each database has its own goals and purposes and as an architect feel free to use the type of database that best solves the specific problem. The points worth mentioning here is that they take into account the CAP theorem and also prevent database information or details from leaking to the API client. Encapsulation is important in many aspects, both in design and in an architectural view. So, it is important to avoid them.

ⅦⅠ CQRS

We come to CQRS or simply Command Query Responsibility Segregation. It is a pattern written by Greg Young in the year 2010 that separates data models a little and their responsibility for writing and reading. With this we have a division of a Command Model, where all writing in the database is done by it and a Query Model, which as the name implies, is where all reading in the database is done by the UI. The image below depicts this architecture well and was taken from Martin Fowler's blog.

Image description

Ⅸ Pagination

Image description

Paging is the process of separating content into discrete pages. Each page has a list of database entities. Paging allows you to retrieve a considerable number of elements from the data store in small blocks, for example, it returns ten pages with a hundred items instead of returning a thousand in large scale in the storage engine. Adding pagination in an API when returning an entity collection allows that computational resources such as network, database and object serialisation are not wasted. There are a few ways to set pagination, for example HATEOAS.

Ⅹ Security

Last but not least, security, the tenth commandment for creating an API. There are several ways to secure your API and here we cover basic authentication and OAuth 2.0.

Basic Authentication: This authentication boils down to username and password and is much more basic.

OAuth2.0: It is an industry-standardised protocol for authorization. And there are some specs like the one below that can be used with OAuth2.0 . So in this session we talk about some self-contained tokens using JWT. For more details we have the site with the JWT specification — https://jwt.io/introduction .

JWT (Json Web Token — RFC 7519) — With JWT we can transport data in compressed and Base64-encoded JSON. With this, it has several attributes known as claims. Information such as token expiration, who authorised the generation of this token, etc. So this specification has the claims divided into three:

  1. Public: Here the attributes can be easily used and defined by an application, for example. Imagine that you need to pass a scope as an attribute, the scope is an attribute that can be used outside the environment and context of the application.
  2. Private: These are private names defined by an Authorization Server or a firm. As an example we have the possibility of having an attribute of the name of the user who authorised its generation.
  3. Registered: Registered claims are native to the specification, such as the token expiration date. They are: iss, sub, aud, exp, nbf, iat and jti.

JWT is a standard format that can be signed and/or encrypted. When it is signed, it uses JSON Web Signature or simply JWS, when encrypted it uses JSON Web Encryption or simply JWE. Its format is composed of 3 sections separated by a period (.). They are: header, payload and signature.

Image description

As you can see, the Header has information about the type of token and whether it was signed or encrypted. The Payload session is where the attributes or claims mentioned above exist. And the last part of the JWT, called the Signature, is the Payload signature used to verify its integrity, ensuring that no one has intercepted and changed it.

JWS (Json Web Signature — RFC 7515) — Defines a process for a signature of a JWT
JWE (Json Web Encryption — RFC 7516) — Defines a process for encrypting a JWT.

-
JOIN OUR COMMUNITY ON DISCORD

For more valuable information and trends to help you keep your data safe, join our community

Image description

Top comments (11)

Collapse
 
decker67 profile image
decker

Nice, to format the code like this

{
  "test": 42
}
Enter fullscreen mode Exit fullscreen mode

use

json

behind the ticks.

Collapse
 
kanekotic profile image
Alvaro • Edited on

nice post, thanks for sharing.

There is one thing I might disagree and is versioning. Versioning tends to be hell to maintain and a big security hole.

I would highly recommend versionless APIs with an expand and contract strategy based on monitoring the use of the changes. With this, you just have a single code space to maintain and reduce the attack surface. This is by design one of the big advantages of graphql over rest APIs.

PS. The area code section is duplicated

Collapse
 
opedroaravena profile image
Pedro Aravena Author

Thanks for your comment, Alvaro! I did not see it was duplicated while editing. I will talk about this advantage of GraphQL over rest APIs more in depth in future articles. Again, thank you for reading and commenting :)

Collapse
 
wpq profile image
Wpq

This is a good article on API design, but **not **about **secure **API design. The security part is microscopic compared to what is needed.
OWASP has some information that can help anyone started.

Collapse
 
opedroaravena profile image
Pedro Aravena Author

Thanks for the comment! You're right, I am actually planning to talk about the OWASP API security project in the next articles! :)

Collapse
 
steelwolf180 profile image
Max Ong Zong Bao • Edited on

OWASP is has a API resources on common attack vectors as well as things you need to look out for building APIs

Collapse
 
t0nyba11 profile image
Info Comment hidden by post author - thread only accessible via permalink
Tony B

I think your article needs a different title, very little of it is about Designing a Secure API.

Collapse
 
seagull29 profile image
Info Comment hidden by post author - thread only accessible via permalink
Erian

Good reading.
However, I think when you mentioned SOA you meant to say SOAP. I mean, SOA (Service Oriented Architecture) is a concept that describes an architecture to build an application, how to structure it in separate parts. Otherwise, SOAP is a set of rules for building services such as an API. According to this, not SOA but SOAP should be compared to REST which is an architectural style just like you wrote.

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Good stuff. Haven't read it, just quickly browsed it. Will try to get time later. Quick note: RESTful API's should return 201 CREATED, not 200 OK whenever a new resource is created.

Collapse
 
opedroaravena profile image
Pedro Aravena Author

Hey, José! Thanks for the comment! When making the request, the HTTP Status of the HTTP response is no longer 200 OK, we get a 201 Created indeed. I wrote about it on Level 2 — HTTP Verbs.

Collapse
 
harikanani profile image
HARIKRUSHN KANANI

Well Explained, Thank you so much for sharing such a great explaination.

Some comments have been hidden by the post's author - find out more

Take a look at this:

Settings

Go to your customization settings to nudge your home feed to show content more relevant to your developer experience level. 🛠