Beyond JWT: Unlocking PASETO for Secure Token Management blog banner image

Beyond JWT: Unlocking PASETO for Secure Token Management

In today's web world, secure connections are essential. JWT (JSON Web Tokens) is widely used for token-based authentication to verify requests, but it has its own disadvantages. New types of token authentication methods are emerging, and one of them is PASETO (Platform-Agnostic Security Tokens). In this article, we will first explore what JWT is and the issues associated with it. Then, we will introduce PASETO, highlighting its differences from JWT. Additionally, we will discuss the different types of PASETO tokens and provide a step-by-step guide on how to generate and validate them.

What is JWT?

JSON Web Token (JWT) is a compact, URL-safe way to transfer information between two parties. It contains three parts: a header, a payload, and a signature. The header includes the token type and signing algorithm, the payload contains details about the user and token like issuer, expiry time, user id, other claims, and the signature verifies the token's authenticity. JWTs are commonly used in authentication systems, where a server generates a token after a user logs in, and the client uses this token to access protected resources. It is a base64 encoded string.

Problems In JWT

1. Widely Misused:

  • Weak Signing Algorithms: JWT offers a variety of signing algorithms, some of which are known to be vulnerable. Developers may mistakenly choose a weak algorithm, leading to security risks.

2. Signing Algorithm:

  • None Algorithm: JWT allows for using "none" as the signing algorithm, effectively disabling signature verification. This can lead to vulnerabilities where attackers can forge or tamper with JWT tokens.

What is PASETO?

PASETO (Platform-Agnostic Security Tokens) is a modern security token format designed to be safer and more straightforward than JSON Web Tokens (JWT). Developed by Scott Arciszewski, PASETO addresses security issues inherent to JWT while promoting simplicity and robustness.

PASETO structure:

v1.local.1_5gaMPojdToeX6Kc6CV751PlwowSrvMUYuVAApH5HOdEh3MSCf3fQ6xsf1_zSprLvcDqUxXZIjNbaaQfpz12d9kmoM8p8oVG5wI-n_4Xghh29eJoVm2kuJ9K4seOBRNWugwqVTTSCwHuJyAM-sWD9sCOIYCrUAa0VR4Z5_XIRbCxvN4xU3aplUoRpZUssFYkxdKWgr3DXc6nD1CkrcE40wUhOk-jGVI7-QSp4_JDcceV7RYigr5q4KFKHRfI65Oznh4ucRAQw

The first section of the PASETO is the protocol version (v1). This tells you what version of the PASETO standard is being used. At the time of writing, there are two versions(v1 and v2) of the PASETO standard.

The second section of the PASETO is the purpose (local). PASETO only defines two purposes for tokens: local or public. I’ll expand on these later. For now, just know that the local purpose is the one I’m demonstrating here.

The third section of the PASETO defines the actual token contents, also known as the payload. It contains details about users and the token itself, such as claims, issuer, and other relevant information.

Uses for PASETO:

you can use PASETOs for two different purposes: symmetric (aka local) and asymmetric (aka public).

Local (Symmetric Encryption)

Local PASETOs are created and encrypted using a secret key, which functions like a long password. If anyone obtains the local PASETO token, they cannot extract any useful information from it without the secret key. As long as the secret key remains secure, the PASETO is safe even if shared publicly.

Use Case: You can use local PASETOs to verify one web service with another. For instance, a microservice architecture where an internal service needs to authenticate requests to another internal service can benefit from using local PASETOs. By encrypting the token with a shared secret key, only the intended service can decrypt and validate the token, ensuring secure communication between services.

Public (Asymmetric Encryption)

Public PASETOs use a pair of public and private keys for encryption. The private key is used to sign the token, and the public key is used to verify its authenticity. This method ensures that the token can be verified by anyone with the public key but only signed by the private key.

Use Case: Public PASETOs are ideal for scenarios where you need to ensure that a token's authenticity can be verified by multiple parties without exposing the private key. For example, in a distributed system where different clients need to validate the token's authenticity, public PASETOs provide a secure solution. Only the server with the private key can sign the token, but any client with the public key can verify it, maintaining the token's integrity and trustworthiness.

Difference between JWT and PASETOs:

jwt-vs-paseto.jpeg

PASETO in Quarkus

To create and use PASETO in a Quarkus project, follow these steps. This example uses Gradle as the build tool, but you can also use Maven if you prefer.

1. Create a Quarkus Project

First, create a new Quarkus project. You can do this using the Quarkus CLI or through the Quarkus website.

2. Add Dependencies

Add the following dependencies to your build.gradle file. For Maven users, search for the corresponding dependencies in the Maven repository and add them to your pom.xml.


    implementation 'dev.paseto:jpaseto-api:0.7.0'
    implementation 'dev.paseto:jpaseto-impl:0.7.0'
    implementation 'dev.paseto:jpaseto-jackson:0.7.0'
    implementation 'dev.paseto:jpaseto-bouncy-castle:0.7.0'

3. Implement PASETO Creation


package com.grootan;

import dev.paseto.jpaseto.Pasetos;
import dev.paseto.jpaseto.lang.Keys;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Random;

@Path("/token/create")
public class CreateTokenService {

    public static final SecretKey SHARED_SECRET = Keys.secretKey();
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String createToken() {
        Instant now = Instant.now();

        String token = Pasetos.V1.LOCAL.builder()
                .setSharedSecret(SHARED_SECRET)
                .setIssuedAt(now)
                .setExpiration(now.plus(1, ChronoUnit.HOURS))
                .setAudience("blog")
                .setIssuer("https://ezto.io/")
                .claim("1d20", new Random().nextInt(20) + 1)
                .compact();

        return token;
    }
}

A PASETO token is then created using the Pasetos.V1.LOCAL.builder(). The following claims and properties are set on the token:

  1. setSharedSecret(SHARED_SECRET): Sets the shared secret key used for encryption.
  2. setIssuedAt(now): Sets the issued-at timestamp to the current time.
  3. setExpiration(now.plus(1, ChronoUnit.HOURS)): Sets the expiration time to one hour from now.
  4. setAudience("blog"): Sets the audience claim to "blog".
  5. setIssuer("https://ezto.io/"): Sets the issuer claim to the specified URL.
  6. claim("1d20", new Random().nextInt(20) + 1): Adds a custom claim, simulating a random dice roll between 1 and 20.

The compact() method finalizes the token creation process, returning the token as a string.

You can make get request to this API to get your PASETO.

4. Implement PASETO Verification:


package com.grootan;

import dev.paseto.jpaseto.Paseto;
import dev.paseto.jpaseto.PasetoParser;
import dev.paseto.jpaseto.Pasetos;
import dev.paseto.jpaseto.Version;
import dev.paseto.jpaseto.lang.Keys;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import java.security.KeyPair;

import static com.grootan.CreateTokenService.SHARED_SECRET;

@Path("/token/verify")
public class VerifyTokenService {

    private static final KeyPair KEY_PAIR = Keys.keyPairFor(Version.V1);

    @POST
    @Produces(MediaType.TEXT_PLAIN)
    public String verifyToken(String token) {
        PasetoParser parser = Pasetos.parserBuilder()
                .setSharedSecret(SHARED_SECRET)
                .setPublicKey(KEY_PAIR.getPublic())
                .requireAudience("blog")
                .requireIssuer("https://ezto.io/")
                .build();

        Paseto result = parser.parse(token);
        return result.getClaims().getIssuer();
    }
}

PasetoParser is built using the Pasetos.parserBuilder() method. The parser is configured with:

  1. setSharedSecret(SHARED_SECRET): The shared secret key used for symmetric encryption.
  2. setPublicKey(KEY_PAIR.getPublic()): The public key used for asymmetric encryption.
  3. requireAudience("blog"): A requirement that the token must have the audience "blog".
  4. requireIssuer("https://ezto.io/"): A requirement that the token must have the issuer "https://ezto.io/". The token is parsed using the parser.parse(token) method, which returns a Paseto object. If the token is valid, this method returns the issuer claim from the token.

You can make a post request with the token to this API to verify your PASETO.

Limitations of PASETO

While PASETO (Platform-Agnostic Security Tokens) offers several improvements over JWT (JSON Web Tokens), it also has its own set of limitations. Here are some key limitations to consider:

Reusability:

  • Not reusable: PASETOs are meant to be single-use tokens and do not have protections against replay attacks. If an attacker intercepts a valid PASETO, they could reuse it to make multiple valid requests, which is not the intended use of PASETOs.

Ecosystem:

  • Not Mature: The ecosystem around PASETO is not as mature as JWT’s. There are fewer implementations and integrations available for various programming languages and frameworks.
  • Limited Documentation: Compared to JWT, there is less documentation, tutorials, and examples available for PASETO, which can hinder learning and adoption.

Learning Curve:

  • New Concepts: For developers familiar with JWT, PASETO introduces new concepts and a different approach to security tokens. This can require additional learning and adjustment.
  • Complexity: Understanding and correctly implementing the PASETO specification can be more complex for developers new to it.

Interoperability:

  • Compatibility: Since JWT is more widely adopted, many systems and services are built around JWT. This can make interoperability with existing systems challenging if they do not support PASETO.
  • Tooling and Infrastructure: Existing tools, middleware, and infrastructure might not support PASETO, requiring additional effort to integrate or replace these components.

Algorithm Support:

  • Fixed Algorithms: PASETO uses fixed algorithms for each version (e.g., AES-256-GCM for local tokens in v1). While this increases security by reducing the risk of weak algorithms, it also limits flexibility for developers who might need different algorithm choices for specific use cases.
  • Version Constraints: PASETO versions (v1, v2, etc.) have strict rules about which algorithms can be used, which might not suit all cryptographic needs.

Specification Evolution:

  • Standard Maturity: As PASETO is relatively new, its specification is still evolving. Future changes to the specification could introduce breaking changes or require updates to existing implementations.

Conclusion

Both JWT and PASETO have their own advantages and disadvantages. As new authentication methods continue to emerge, developers will have more alternatives to explore. It is important for us to stay informed about these options and carefully evaluate which method best suits our application's needs. By understanding the strengths and weaknesses of JWT and PASETO, we can make more informed decisions to enhance the security and efficiency of our applications.

Related Posts