Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
mangeshpise
Product and Topic Expert
Product and Topic Expert
The phrase, "Data is the new oil," quickly resonated with the masses when British mathematician Clive Humbly first coined it in 2006. Michael Palmer described the similarities between oil and data. Michael indicated that just like crude oil, data has no intrinsic value early on until it gets cleaned and refined, which makes it ready for consumption (Palmer, 2006). Consumption-ready data is valuable and can be shared in exchange for value. For example, coded medical records are of immense value to hospitals as it helps them identify effective clinical pathways to treat their patients. Point-of-Sales (POS) datasets from retail stores and e-commerce platforms are utilized to identify upsell opportunities. Rich enterprise datasets can also be used to create headless content management systems (CMS), which enable a rich user experience. For example, targeted product details, pricing, customer reviews, etc., can be seamlessly provided to prospective customers via multiple interfaces such as mobile notifications, web pages, mobile apps, intelligent assistants, etc.

The most common approach to sharing such data is via APIs. Frontend applications or backend services can request real-time data via APIs and either presents them on a user interface or process them via backend services to support a business process. For instance, by leveraging APIs from your choice of a payment processor, a developer can easily enable PCI-compliant online payment capability on its app or website.

However, such APIs must be restricted to authorized users or members, typically external consumers. And that's where API security comes into play. This blog post will focus on using the upcoming JSON Web Tokens (JWT) standard (RFC 7519) to protect your APIs. Although the concepts can be applied to both internal-facing and external-facing APIs, the perspective maintained in this blog post is the latter, just to help you maintain a consistent frame of mind. Do note that it takes much more than just a token to secure the APIs completely. However, to prevent getting lost in all the moving parts around securing APIs and web applications, this blog post strictly explains how tokens can be used for authenticating API calls.

The most common approach that might come to one's mind would be to have the actual API / Web Service perform authentication based on some information passed during the API call. Two such implementations are Basic Authentication and API-Key based authentication. Both these are discussed below, but it is essential to understand the impact of this approach on the overall scalability aspect of the architecture. And that is another purpose of this blog post I'd like to clarify, which has to do more with the impact of JWT-based security on the overall scalability aspect of the architecture versus discussing comparisons between multiple authentication methods.

Scaling issue with API Authentication


Because REST APIs are stateless, there is no way for the service to know about your previous calls, nor can you have it remember you for subsequent calls. Each call is independent. If the caller must be authenticated repeatedly with every API call, the authentication server, responsible for the actual username and password authentication, is hit every time the API is called. Please look at Fig. 1(A) below, which shows a generic API request/response flow that includes an authentication step.

There are two scenarios in which scaling can become a major concern. Each of these are described below.

Scaling Scenario 1: Imagine one customer that calls your API setup using Basic authentication at least 100 times everyday. The total number of calls to the authentication server will also be at least 100 per day since each stateless API call needs to be authenticated individually.


Fig. 1(A): Single API request-response with authentication


If we scale our customers by double (i.e., 2 customers), the number of API calls will now be 200 per day, meaning the calls to the authentication server will also double, becoming 200 calls per day. So, if you need to increase the capacity in your API infrastructure, even the authentication server capacity needs to be upscaled.

Scaling Scenario 2: Imagine you have two externally exposed APIs secured via Basic authentication against a single authentication server. Let's also assume that our single customer calls each of the APIs 100 times per day. Under these situations, the calls to the authentication servers will be a summation of authentications between both APIs, i.e., 200 authentications per day.


Fig. 1(B): Multiple APIs request-response with authentication


Just like in scenario 1, if our customer base doubles to 2 customers, who also call both the APIs 100 times a day, the total API calls would be somewhere to the order of -

2 customers x (100 calls x 2 APIs) = 400 calls per day. Effectively meaning that the calls to the authentication server will also be 400 calls per day.

In conclusion, although each of the API services can be scaled individually based on the volume of calls, the authentication server must be scaled considering the total API call volume. It also quickly becomes the single point of failure of the entire architecture.

Authentication Methods and Challenges


As mentioned earlier, there are multiple authentication methods to choose from. Each authentication method has its pros and cons and applicability to the use case. For example, an API used to retrieve a user's profile image from social media applications could do with Basic authentication or API key-based authentication, but retrieving a user profile from the bank would need an elevated level of security and authentication method to safeguard it from falling into the wrong hands.

Basic Authentication


As the name suggests, it is the basic form of authentication involving a username and password. However, instead of sending it as plain text over the network, it is base64 encoded (not encrypted) and attached to the header of the HTTP request. For example, the following REST API used to retrieve the contacts list can be called using Basic Authentication.

Assume the username is 'hello' and the password is 'w0rL%'; the following commands would be used to call the API.
$ echo 'hello:w0rL%' | base64 
aGVsbG86dzByTCUK

$ curl -X GET https://api-examples.com/contacts \
-H 'Authorization: Basic aGVsbG86dzByTCUK'

There are a couple of challenges with this approach. First, although the username and password are base64 encoded, they are entirely reversible.
$ echo 'aGVsbG86dzByTCUK' | base64 --decode
hello:w0rL%

Secondly, as discussed earlier, this approach may lead to issues with scalability due to an overwhelmed authentication server. Potentially this may lead to slow API response times and/or failures due to either timeouts or failed authentication servers.

API Key Authentication


Instead of relying on username and password directly in this authentication method, the developer obtains a unique hashed string from the service provider before initiating development. This hashed string is called the API Key. The API provider can map and validate the unique API Keys with a developer. Depending on the API provider, a developer may be able to generate multiple API Keys. This provides the ability to use them strategically depending on specific areas of their application and revoke or modify them at a certain frequency. Thus, in comparison to Basic authentication where the developer needs to rely on changing the password frequently and using a single user across different APIs, API Key-based authentication provides a one-to-many mapping of a user to API keys and allows revoking them individually without impacting usage across all APIs.


Fig. 2: API Key-based authentication flow


The API Key validation can occur against a database that stores the user to API key mappings. With each API call, the API key and its validity can be looked up from this database, thus eliminating frequent calls to the authentication server and potentially eliminating it from becoming a single point of failure. To improve the response times required to lookup the API Keys, a key-value or document database can be leveraged.

Although this is an improvement over the Basic authentication, API Keys still have a disadvantage in that it needs to be manually changed, increasing overhead and associated code maintenance. Another disadvantage is that the API Keys are still sent as clear text via query parameters or headers, and there is always fear of them falling in the wrong hands. Lastly, the scalability issue of authentication (with frequent API Key lookups) remains; it just shifts from the authentication server to the API Key database.

Token Based Authentication


A token, in this context, is a string of random characters of varying lengths requested from an authentication server. API calls using the token-based authentication method involve three steps, as detailed below.


Fig. 3: Token-based authentication flow


In the first step, an authenticated developer needs to register their application with the API provider in exchange for credentials in the form of a client ID and Secret.

Using the registered client id and secret, in the second step, the developer then requests a token before initiating the call to the business API. A signed token is issued by the authentication server with a specific validity period.

In the subsequent steps, while the token is valid, appropriate business API endpoints are called while passing the token received in the second step. The API endpoints verify the tokens and respond back with an appropriate functional response. The verification of the token does not rely on database lookups or hitting the authentication server, unlike in the case with API Key based authentication. Instead, it verifies the signature of the token using secrets or encryption keys.

An analogy that can be used to understand this in a better way would be that of the driver's license issued by the Bureau/Department of motor vehicles (BMV or DMV). The motor vehicle agency (authentication server) issues a driver's license (token) with a special verifiable seal (signature) after performing multiple points of validations based on original documents (authentication). From that point on, until the driver's license is valid, we can simply present the driver's license at appropriate places, such as issuing a boarding pass at airports, opening bank accounts, etc. The employees at airports and banks do not need to validate who we are (identity). Instead, they rely on verifying that the driver's license is valid by checking the seal (signature) and thus inferring that we are authorized to use their service (i.e., board the plane or open the bank account).

As you can find, the token-based authentication method provides high scalability since there are no database lookups for token validation. In fact, the proof is more about the token's authenticity and is resolved by validating the token's signature. We'll cover the signatures in depth in the next section, but for now, please understand that for the token to be declared valid, the signatures generated by the authentication server and that received by the API server from the caller must match. The token-based authentication method also forces expiration and requesting new tokens (a.k.a. token rotations), ensuring added security in the design.

JSON Web Tokens (JWTs)


JWT (also pronounced as "jot") is a standard that emerged between 2011 and 2016 from the JSON Object Signing and Encryption (a.k.a. JOSE) group (Peyrott, 2018). JWT standard enables sharing information (e.g., security information) between two parties using compact and self-contained artifacts. So, JWT can compactly share any information with an in-built trust mechanism via signing. Still, the most common implementation of this standard is found in securing APIs. Since JSON (JavaScript Object Notation) is a lightweight data-interchange format, it has been leveraged in exchanging security claims between a client and server over stateless communication.
Sidebar: JWT (JSON Web Tokens) often operate with other standards, such as -

  • JWS (JSON Web Signatures) for signing JSON content with digital signatures

  • JWK (JSON Web Keys) as JSON formatted cryptographic keys and key sets

  • JWE (JSON Web Encryption) for encrypting and/or decrypting JSON content

  • JWA (JSON Web Algorithms) for representing cryptographic algorithms used in JWS, JWE, and JWK



Security claims are assertions made about something. Multiple standard claims are available in the JWT specification, allowing interoperability between various parties. One of the standard claims in JWTs is called the "issuer" (specified as "iss" in the standard) of the token. To understand this, refer back to the driver's license example. A valid driver's license claims that it is issued by a specific BMV / DMV. This allows a verifier to acknowledge that a person from a particular state in the United States is certified by the state issuing his driver's license. In other words, the claim of being from a specific state is backed by their driver's license issuer.

Some other claims in the standard JWT specification are:

  • alg: specifies the algorithm used to sign the JWT token.

  • sub: specifies the subject or the party for whom the token is issued.

  • aud: specifies the identity of the intended recipient at the subject.

  • exp: specifies the expiration date/time of the issued token

  • iat: specifies the date/time when the token was issued.


JWT also allows adding user-defined claims, but for the purpose of this blog post, we will stay within the standard claims.

The JWT structure contains three parts separated by a dot (.). For example, the following compact, plaintext JWT represents the JSON object shown.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

{ "alg": "HS256", "typ": "JWT" }.
{ "sub": "1234567890", "name": "John Doe", "admin": true }.
<signautre>

The first part is the header which is the base64 encoded string of the JSON object representing the algorithm used to sign the JWT. The second part is the base64 encoded string of the JSON object representing the payload, which contains standard and user-defined claims. The last part is the signature generated using a concatenated header and payload, signed using a secret known only to the parties exchanging this token.
signing_algorithm(<header>.<payload>) = <signature>

Thus, you can notice that the signature provided by the authentication server (Steps 2 & 3 in Fig. 3), which is embedded in the JWT issued during the initial authentication process, must be verified by the API using the same secret (Step 4 in Fig. 3) used by the authentication server. And if the signatures match, the API can ascertain that the token's issuer was successfully authenticated by the caller and can therefore be trusted with the response.

Example of API authentication using JWT


An example is always the best way to see how JWTs are created and used. Our example below caters to a situation where an organization has exposed some of its business capabilities as APIs externally for its partners and customers. The partners and customers would use the organization's APIs as a part of their end-to-end process, enabling a seamlessly integrated process for their customers or users.

Thus, there are two separate personas in this example, as follows. Please refer to Fig. 3 again for visually following the responsibilities of these personas.

  1. The API provider is responsible for providing an API endpoint with the logic to provide the business functions to its callers. The API provider is also responsible for providing security and access controls to the exposed APIs. This is achieved by placing an authentication server that performs three functions listed below:

    • Register an "application" (one-time activity) against which it generates a client ID and Secret. These are shared with the application developer. The developer needs to be authenticated while registering the application.

    • Generates JWTs signed using the secret from the previous step. JWTs must be set to expire within a considerable amount of time, depending on application requirements.

    • Once the API call is received from the caller along with the JWT supplied in the header (Authorization: Bearer token), the API endpoint must verify the signature before responding to the request.



  2. The API caller who intends to use the API endpoints with business functions and use that within their own application. For the purpose of this example, we will be skipping the application aspect of the solution. To be able to call the API endpoints, the developer needs to perform the following steps:

    • One-time registration of the "application" with the API provider and obtaining the client ID and Secret.

    • Hit the authentication server endpoint to request a signed JWT. The caller needs to provide the client ID and Secret.

    • Use the signed JWT to call the API endpoint and handle the response appropriately per the requirements of the application being built.




Note: Any code snippets shown in the example below must be used only to gain an understanding of the JWT concepts used in the authentication. None of the code provided below is production-ready code.

Endpoints


Along the same lines, the code is organized as two distinct services:

  1. Authentication Service, with endpoints for:

    • registering the app, and
      <auth server>/register


    • generating tokens
      <auth server>/auth




  2. API endpoint with business function:
    <api server>/example



Dependent Services & Modules


For the purpose of this example, we will be using the jsonwebtoken npm module to sign and verify the JWTs. At the time of this blog post, jsonwebtoken has had over 10 million downloads and has active contributions.

To save the client ID and Secret in a secure store, we have leveraged the SAP Credential Store service on the SAP Business Technology Platform (SAP BTP). Keys and passwords are signed using public and private keys and compacted using JWE. Configuration and setup of this service are out-of-scope of this blog post (for now!).

Service 1: Authentication Service


Registration


const app = require("express")();
const jwt = require("jsonwebtoken");
const crypto = require('crypto');
const axios = require('axios');
const credstore = require('./credstore'); // -- Custom module not shown in this blog post

/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Generate a clientID and secret. Save in Credential Store.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
app.post('/register', (req, res) => {
let headers = req.headers;
authenticateUser(headers.device_code).then((user) => {
if (user) {
let client_id = crypto.randomUUID();
let registration = {
"app_name": headers.app_name,
"client_id": client_id,
"authorized_by": user
};

/* -- -------- -------- -------- -------- -------- --------
Note: we use the SAP Credential Store service to persist
the client ID as the key, and generate a random value as the secret.
// -- -------- -------- -------- -------- -------- -------- */

credstore.writeKey(headers.app_name, client_id, registration)
.then((keyValue) => {
registration['secret'] = keyValue.value;
credstore.readKey(headers.app_name, client_id)
.then((data) => {
registration['client_id'] = data.name;
registration['secret'] = data.value;
res.status(200).json(registration);
}).catch((err) => res.status(400).json(err));
}).catch((err) => res.status(400).json(err));
} else res.status(401).json({
"status": "Unauthorized"
});
});
});

/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Authenticate user so application can be registered.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
let authenticateUser = (device_code) => {

/* -- -------- -------- -------- -------- -------- --------
implement whatever way of authenticating user, e.g. oauth
add application scope and roles to request headers
// -- -------- -------- -------- -------- -------- -------- */

};

API Authorization (Request JWT)


const app = require("express")();
const jwt = require("jsonwebtoken");
const credstore = require('./credstore'); // -- Custom module not shown in this blog post

/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Use client_id and secret to acquire a token.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
app.post('/auth', (req, res) => {
let headers = req.headers;
if (!headers.client_id || !headers.secret || !headers.app_name)
res.status(403).json({
"status": "Forbidden",
"message": "Missing client_id and secret in request header"
});
else {
credstore.readKey(headers.app_name, headers.client_id).then((data) => {
if (data.value == headers.secret) {
let payload = {
"owner": JSON.parse(data.metadata).authorized_by.email
};

/* -- -------- -------- -------- -------- -------- --------
.. If client ID and secret match in the SAP Credential Store,
.. we create a new token and sign it using the same secret.
.. We also set following claims so it can be verified by the API:
.. - specify an approved algorithm to generate the signature,
.. - set an expiration period for the token,
.. - set authentication server as the issuer of the token,
.. - address it for a specific audience (app_name), and
.. - mention the client_id as the subject.
// -- -------- -------- -------- -------- -------- -------- */

jwt.sign(payload, headers.secret, {
algorithm: 'HS256',
expiresIn: (headers.token_expiration_mins || 60) * 60,
issuer: process.env.VCAP_APPLICATION ?
JSON.parse(process.env.VCAP_APPLICATION).application_uris[0]
: "localhost",
audience: JSON.parse(data.metadata).app_name,
subject: JSON.parse(data.metadata).client_id
}, (err, token) => {
err ? res.status(500).json({
"status": "Internal error",
"message": err
}) : res.status(200).json({
"status": "Token generated",
"token": token
});
});
} else res.status(403).json({
"status": "Forbidden",
"message": "Invalid client_id or secret provided"
});
});
}
});

 

Service 2: API authentication


let app = require("express")();
let jwt = require("jsonwebtoken");
const credstore = require('./credstore'); // -- Custom module not shown in this blog post

/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
API call: must token
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
app.get('/example', verifyJWT, (req, res) => {
res.status(200).json({
"status": "Authorized",
"payload": JSON.parse(res.getHeader('payload'))
});
});

/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Validate token to secure API calls.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
let verifyJWT = (req, res, next) => {
let headers = req.headers;
if (!headers.authorization)
res.status(403).json({
"status": "Forbidden",
"message": "Missing Authorization in request header"
});
else {
let token = headers.authorization.split(" ")[1];
let payload = jwt.decode(token);
credstore.readKey(payload['aud'], payload['sub']).then((data) => {
let secret = data.value;

/* -- -------- -------- -------- -------- -------- --------
.. We verify the token using the secret from SAP Credential Store.
.. We also validate that the signature:
.. - using an approved algorithm,
.. - that it was issued by an expected issuer, and
.. - that it was meant for a specific audience (app_name).
// -- -------- -------- -------- -------- -------- -------- */

jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: process.env.VCAP_APPLICATION ?
"authserver.cfapps.us10-001.hana.ondemand.com" : "localhost",
audience: headers.app_name
}, (err, jwtPayload) => {
if (err) res.status(401).json({
"status": "Unauthorized",
"message": err
})
else {
res.set('payload', JSON.stringify(jwtPayload));
next();
}
});
});
}
};

 

Customer & Partner PoV (Consuming your APIs)


It would not be a full circle for this blog post if I do not cover the customer and partner point-of-view as consumers of this API. Remember, from an external perspective, the ultimate goal is to use the API endpoint - /example, which needs a token for authentication. But in order to authenticate, they start with one-time application registration to be able to request the token from /auth endpoint. Although this can happen programmatically, I have shown the individual calls below using curl. That would also give an opportunity to see the steps in detail and understand the whole process from a customer's or partner's perspective.

Register application


$ curl -X POST https://authserver.cfapps.us10-001.hana.ondemand.com/register \
-H 'device_code: AH-1Ng0nVCeaP3bHykE7MUJY2RXwFyZMWcuXi6f-n1iCTmbjXPEaBlNER63XTxM4fxEyqhJ9sPQbtUwBVSGnkfv1B5f_OhJ00A'

{
"app_name": "mangesh",
"client_id": "6ad31744-b5bb-4767-8ffc-5e4a56d70a6f",
"authorized_by": {
"id": "111787328160345054",
"email": "mppxxx@gmail.com",
"verified_email": true,
"name": "Mangesh Pise",
"given_name": "Mangesh",
"family_name": "Pise",
"picture": "https://lh3.googleusercontent.com/a/AEdFTp7NIerQySLNH8lOc7N4Xfn5RclDy4dVLZvb1WKPTBs=s96-c",
"locale": "en"
},
"secret": "Pd4IS3/esAXP6Z0eOMj34Del44nxwv6uiRMyRoDNhfs="
}

Use application credentials to request token


$ curl -X POST https://authserver.cfapps.us10-001.hana.ondemand.com/auth \
-H 'app_name: mangesh' -H 'client_id: 6ad31744-b5bb-4767-8ffc-5e4a56d70a6f' -H 'secret: Pd4IS3/esAXP6Z0eOMj34Del44nxwv6uiRMyRoDNhfs=' \
-H 'token_expiration_mins: 10'

{"status":"Token generated",
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvd25lciI6Im1wcGlzZUBnbWFpbC5jb20iLCJpYXQiOjE2NzM0OTUyNjMsImV4cCI6MTY3MzUwMjQ2MywiYXVkIjoibWFuZ2VzaCIsImlzcyI6ImF1dGhzZXJ2ZXIuY2ZhcHBzLnVzMTAtMDAxLmhhbmEub25kZW1hbmQuY29tIiwic3ViIjoiNmFkMzE3NDQtYjViYi00NzY3LThmZmMtNWU0YTU2ZDcwYTZmIn0.LdJ_Be3RR9IHYbYM-i1R_qSG0aYuEgev254yaIjgO10"}

Use the above token to call the API endpoint


$ curl -X GET https://api.cfapps.us10-001.hana.ondemand.com/example \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvd25lciI6Im1wcGlzZUBnbWFpbC5jb20iLCJpYXQiOjE2NzM0OTUyNjMsImV4cCI6MTY3MzUwMjQ2MywiYXVkIjoibWFuZ2VzaCIsImlzcyI6ImF1dGhzZXJ2ZXIuY2ZhcHBzLnVzMTAtMDAxLmhhbmEub25kZW1hbmQuY29tIiwic3ViIjoiNmFkMzE3NDQtYjViYi00NzY3LThmZmMtNWU0YTU2ZDcwYTZmIn0.LdJ_Be3RR9IHYbYM-i1R_qSG0aYuEgev254yaIjgO10'

{"status":"Authorized","payload":{"owner":"mppise@gmail.com","iat":1673495263,"exp":1673502463,"aud":"mangesh","iss":"authserver.cfapps.us10-001.hana.ondemand.com","sub":"6ad31744-b5bb-4767-8ffc-5e4a56d70a6f"}}

 

Conclusion


Token-based authentication has enabled us to not only elevate the security posture in protecting our APIs from unauthorized access but has also scaled the API-based data interchange architecture by decoupling the authentication process from the token verification process. Where the JWT standard enhances the concept further lies in its ability to create a compact plaintext payload that can be passed over the internet without much additional overhead. In addition, the concept of signing the payload using symmetric and asymmetric encryption and passing that as a part of a single token is critical to be able to add multiple layers of security from an end-to-end solution architecture perspective.

JWT-based tokens have become a widely adopted standard in the API authentication area. In fact, we can experience it from the fact that this same approach was required in our example when we leveraged SAP Credential Store service from SAP BTP as well as Google Identity authentication service.

Are you finding JWTs to become mainstream in securing your integration use cases? If so, please share if there are any additional use cases outside of API authentication. I would also be curious to learn if there are any challenges you have come across in using JWTs in your organization.

Are you a developer or integration designer who already know his way around SAP BTP? Then find out how to build integrations from and to cloud applications SAP’s free learning content on SAP Integration Suite. Check out even more role-based learning resources and opportunities to get certified in one place on SAP Learning site.

 




References


Palmer. (2006, November 3). Data is the New Oil. ANA Marketing Maestros: Data Is the New Oil. Retrieved January 5, 2023, from https://ana.blogs.com/maestros/2006/11/data_is_the_new.html

Jones, M., Bradley, J., & Sakimura, N. (2015, May). RFC 7519: JSON Web Token (JWT). RFC 7519: JSON Web Token (JWT). Retrieved January 9, 2023, from https://www.rfc-editor.org/rfc/rfc7519

JWT.IO. (n.d.). JSON Web Tokens - jwt.io. Retrieved January 10, 2023, from http://jwt.io/

Peyrott. (2018). JWT Handbook. Auth0 Inc. https://auth0.com/resources/ebooks/jwt-handbook

Barnes, R. (2014, April). RFC 7165: Use Cases and Requirements for JSON Object Signing and Encryption (JOSE). RFC 7165: Use Cases and Requirements for JSON Object Signing and Encryption (JOSE). Retrieved January 11, 2023, from https://www.rfc-editor.org/rfc/rfc7165

 
2 Comments