Color Mode


    Language

An overview of PASETO Token-Based Authentication

May 16, 2023

When it comes to securing web applications, there are various options for authentication and authorization. Two popular choices are PASETO and JWT.

While JWT has been widely utilized for many years, PASETO is a relatively new technology that has gained traction due to its enhanced security features.

PASETO (Platform-Agnostic Security Tokens) is both a specification and a reference implementation for secure stateless tokens. It serves as a highly secure alternative to JWT.

Understanding Token-Based Authentication

The typical flow for authenticating a user in a secured API involves the following steps:

  1. The user provides their username and password for authentication.
  2. Upon successful verification, the API returns an access token (either JWT or PASETO).
  3. The access token is included in the Authorization header when making requests to protected endpoints.
  4. The API server validates the token and responds with the appropriate secured data if the access is valid.

Modern Token Base Authentication

Understanding JWT and Its Limitations

JWT consists of three parts:

  • The header, which contains the token's signing algorithm.
  • The payload, which holds information about the authenticated user and additional data. The server can customize this part of the payload.
  • The signature, generated by the server using a private key. This signature enables the server to verify the authenticity of the JWT during the validation process.

![JWT Demo](/assets/img/articles/2023-05-16-An-overview-of-PASETO-Token-Based-Authentication/jwt.webp

Photo by Wallarm

While JWT provides flexibility in choosing the digital signature algorithm and verification implementation, this flexibility also introduces vulnerabilities.

There are multiple algorithms to choose from, some of which may be weak and susceptible to attacks, such as ECDSA (vulnerable to invalid-curve attacks) or RSA PKCSv1.5 (vulnerable to padding oracle attacks).

JWT implementations are also prone to errors, which can result in security vulnerabilities like broken JWT validation.

When used correctly, JWT can be a reliable and flexible authentication system. However, caution must be exercised to avoid exposing the server to potential attacks.

In contrast, PASETO addresses these issues by simplifying the implementation process.

PASETO: The Solution

While JWT offers implementation flexibility, PASETO takes a more rigid approach. However, this rigidity helps prevent implementation errors and misuse.

PASETO is designed to be user-friendly and offers higher cryptographic resilience compared to JWT.

When using PASETO, the user only needs to configure two settings:

  • The PASETO version (v1, v2, v3, or v4) specified via the version field of the token.
  • Whether encryption and decryption should be symmetric or asymmetric, indicated by the purpose field of the token.

That's all it takes.

PASETO Token Structure

Similar to JSON Web Tokens (JWTs), PASETO tokens are composed of dot-separated base64url encoded data organized in the following format:

version.purpose.payload.footer
  • version: Allows for incremental improvements to the token format. The current versions are "v1," "v2," "v3," and "v4."
    • v1: Utilizes strong cryptographic primitives that are widely available today.
    • v2: Utilizes newer and stronger cryptographic primitives, but is supported by fewer cryptographic libraries.
  • purpose: A concise string that describes the token format as either "local" or "public."
    • local: The token's payload is encrypted and can only be accessed by parties possessing the shared key.
    • public: The payload is NOT encrypted; instead, it is signed and verified using a public key.
  • payload: Encoded data with a format specific to the token's version and purpose.
  • footer (optional): Unencrypted JSON, typically used to store the ID of a public key for token validation.

All PASETO token formats are tamperproof, ensuring that any modifications to the token result in failed validation.

Local Tokens (Symmetric Encryption)

Local tokens are always symmetrically encrypted using a shared secret key. This means that the contents of a local PASETO token cannot be viewed without the correct secret key.

![Symmetric encryption](/assets/img/articles/2023-05-16-An-overview-of-PASETO-Token-Based-Authentication/symmetric.webp

Here is an example of a local PASETO token, including its decoded payload, optional footer, and the signing key used to sign the information.

Symmetric encryption

Public Tokens (Asymmetric Encryption)

Public PASETO tokens are suitable for scenarios where it is not safe to share a secret key with all involved parties.

Public tokens are not encrypted but are digitally signed. This means that if an attacker obtains a public PASETO token, they can view its contents but cannot modify it without detection due to the digital signatures used in PASETO tokens.

If an attempt is made to verify a maliciously modified public PASETO token, an error will occur.

![Asymmetric encryption](/assets/img/articles/2023-05-16-An-overview-of-PASETO-Token-Based-Authentication/asymmetric.webp

Here is an example of a public PASETO token, including its decoded payload, optional footer, and the public and private keys used to sign the information.

Asymmetric encryption

Versions

Each PASETO version introduces improvements over its predecessor. To implement the PASETO specification correctly, refer to the details provided for each version:

https://github.com/paseto-standard/paseto-spec/tree/master/docs/01-Protocol-Versions

Libraries

You can find all the libraries implementing PASETO for all the popular languages along with the supported versions here:

https://paseto.io/

Implementation using the Ruby library

We will follow an example of usage of the Ruby library implementing PASETO:

https://github.com/bannable/paseto

We need to first install the gem.

gem 'ruby-paseto'
gem 'rbnacl', '~> 7.1.1' # optional - only if PASETO version 4 will be used

Symmetric encryption (local)

require 'paseto'

####################
#### ENCRYPTION ####
####################

# typically, this shared 32 bytes key is stored in both the authentication server
# and the client server
shared_secret_key = SecureRandom.bytes(32)

# initialize the PASETO encrypter/decrypter
crypt = Paseto::V4::Local.new(ikm: shared_secret_key) # version: v4 / purpose: local

# payload in plain text
claims = { "company" => "monstarlab" }
footer = { "viewable" => "yes" }

# encode the payload and get the PASETO
encrypted_token = crypt.encode(claims, footer: JSON.dump(footer))
# => "v4.local.E1Y_KQ6Ek8lSOKrJ6kI1YjWXfAKJ0OEcdhUPywznjBjK5SGDUr4-6rbaZk-CIM_mdQgQHGPj8yAWQswktkCe_Sm_Nj9eEfDxNBFeAC2KsgqFCjF07VJo5ail0jnSTNM0-PekMytJleea8OvNkKdoLs4GKAsZTTJ_-DEMmOMyVlddlmaWoVwnF2JkpjBzFRO7d6PlIIY29rQWOXSvZxoLEqkE5XJvHpFs4NTuCHnF4Pko10X_sgHCPkTGXkWNDg.eyJ2aWV3YWJsZSI6InllcyJ9"

####################
#### DECRYPTION ####
####################

# typically, this shared 32 bytes key is stored in both the authentication server
# and the client server
shared_secret_key = SecureRandom.bytes(32)

# initialize the PASETO encrypter/decrypter
crypt = Paseto::V4::Local.new(ikm: shared_secret_key) # version: v4 / purpose: local
encrypted_token = "v4.local.E1Y_KQ6Ek8lSOKrJ6kI1YjWXfAKJ0OEcdhUPywznjBjK5SGDUr4-6rbaZk-CIM_mdQgQHGPj8yAWQswktkCe_Sm_Nj9eEfDxNBFeAC2KsgqFCjF07VJo5ail0jnSTNM0-PekMytJleea8OvNkKdoLs4GKAsZTTJ_-DEMmOMyVlddlmaWoVwnF2JkpjBzFRO7d6PlIIY29rQWOXSvZxoLEqkE5XJvHpFs4NTuCHnF4Pko10X_sgHCPkTGXkWNDg.eyJ2aWV3YWJsZSI6InllcyJ9"

# note that the last part of the token `eyJ2aWV3YWJsZSI6InllcyJ9` is only a base64 encoded string
# meaning anyone can see its contents
Base64.decode64("eyJ2aWV3YWJsZSI6InllcyJ9")
# => "{\"viewable\":\"yes\"}"

# decoding the token and get the payload
crypt.decode(encrypted_token)
# => <Paseto::Result
#           claims={
#              "exp"=>"2023-05-10T11:18:41+09:00",
#              "iat"=>"2023-05-10T10:18:41+09:00",
#              "nbf"=>"2023-05-10T10:18:41+09:00",
#              "company"=>"monstarlab"},
#           footer={"viewable"=>"yes"}
#    >

# if the token has been maliciously modified, an error will be raised
encrypted_token[-1] = "M"
crypt.decode(encrypted_token)
# Paseto::InvalidAuthenticator: Paseto::InvalidAuthenticator
# from /Users/tony_duong/.rvm/gems/ruby-3.1.3/gems/ruby-paseto-0.1.2/lib/paseto/symmetric_key.rb:53:in `decrypt'

Usage of asymmetric encryption (public)

We first start by generating a public/private key pair.

ssh-keygen
# Output: public key and private key
require 'paseto'

####################
#### ENCRYPTION ####
####################

# initialize the PASETO encrypter/decrypter
pem = File.read('private_key')
signer = Paseto::V4::Public.new(pem)

# payload in plain text
claims = { "company" => "monstarlab" }
footer = { "viewable" => "yes" }

# encode the payload and get the PASETO
signed_token = signer.encode(claims, footer: footer)
# => "v4.public.eyJleHAiOiIyMDIzLTA1LTEwVDExOjQ2OjA0KzA5OjAwIiwiaWF0IjoiMjAyMy0wNS0xMFQxMDo0NjowNCswOTowMCIsIm5iZiI6IjIwMjMtMDUtMTBUMTA6NDY6MDQrMDk6MDAiLCJjb21wYW55IjoibW9uc3RhcmxhYiJ9taKQPCARAZHv85xk7yaWPDeWHaHt981eHmoiYIrIcA-monnIbMax2EDxIjObgr6qLLuYzAH4BK5N6q0TJANeBg.eyJ2aWV3YWJsZSI6InllcyJ9"

####################
#### DECRYPTION ####
####################

verifier = Paseto::V4::Public.new('public_key')

# when initialized with a public key, only verification/decoding can be performed
# if encode is called, error is raised
verifier.encode({'foo' => 'bar'})
# => ArgumentError

signed_token = "v4.public.eyJleHAiOiIyMDIzLTA1LTEwVDExOjQ2OjA0KzA5OjAwIiwiaWF0IjoiMjAyMy0wNS0xMFQxMDo0NjowNCswOTowMCIsIm5iZiI6IjIwMjMtMDUtMTBUMTA6NDY6MDQrMDk6MDAiLCJjb21wYW55IjoibW9uc3RhcmxhYiJ9taKQPCARAZHv85xk7yaWPDeWHaHt981eHmoiYIrIcA-monnIbMax2EDxIjObgr6qLLuYzAH4BK5N6q0TJANeBg.eyJ2aWV3YWJsZSI6InllcyJ9"
verifier.decode(signed_token)
# => <Paseto::Result
#           claims={
#              "exp"=>"2023-05-10T11:18:41+09:00",
#              "iat"=>"2023-05-10T10:18:41+09:00",
#              "nbf"=>"2023-05-10T10:18:41+09:00",
#              "company"=>"monstarlab"},
#           footer={"viewable"=>"yes"}
#    >

Conclusion

In conclusion, we have explored the vulnerabilities that can arise from the careless use of JSON Web Tokens (JWTs). While JWTs can serve as an effective means of incorporating authentication into a system when used correctly, their flexible specification can potentially lead to implementation errors and subsequent security issues.

To address these concerns, PASETO has been introduced as an alternative solution with specific design goals in mind:

  • Simplicity of use: PASETO simplifies the token creation process by requiring only the specification of the purpose and version parameters.
  • Resistance to implementation errors: Unlike JWTs, PASETO eliminates the need to choose from a wide range of potentially insecure cryptographic algorithms, thereby reducing the risk of implementation mistakes.

PASETO takes a developer-first approach to security tokens by streamlining the decision-making process for developers. By offering two distinct purposes, namely the choice between a symmetric or asymmetric security model, PASETO automatically selects the most suitable options for authenticated encryption and digital signatures. This ensures that your tokens remain secure and immune to cryptographic vulnerabilities.

Overall, PASETO provides a more robust and straightforward approach to security token management, mitigating the risks associated with JWTs while maintaining a high level of security for your system.

References

  • PASETO: The JWT Killer? - article by Sandesh Dahake
  • PASETO: Platform-Agnostic Security Tokens - Github repository
  • Why PASETO is better than JWT for token-based authentication? - Youtube video by Tech School
  • Introducing JPaseto: Security Tokens For Java - article by Brian Demers
  • Encode or Decode PASETO
  • Paseto - Github repository

Article Photo by Erik Mclean

backendsecurityauthenticationpasetojwt

Author

Tony Duong

Tony Duong

Backend Developer

Currently on the never-ending learning journey 🚀

You may also like

November 7, 2024

Introducing Shorebird, code push service for Flutter apps

Update Flutter apps without store review What is Shorebird? Shorebird is a service that allows Flutter apps to be updated directly at runtime. Removing the need to build and submit a new app version to Apple Store Connect or Play Console for review for ev...

Christofer Henriksson

Christofer Henriksson

Flutter

May 27, 2024

Introducing UCL Max AltPlay, a turn-by-turn real-time Football simulation

At this year's MonstarHacks, our goal was to elevate the sports experience to the next level with cutting-edge AI and machine learning technologies. With that in mind, we designed a unique solution for football fans that will open up new dimensions for wa...

Rayhan NabiRokon UddinArman Morshed

Rayhan Nabi, Rokon Uddin, Arman Morshed

MonstarHacks

ServicesCasesAbout Us
CareersThought LeadershipContact
© 2022 Monstarlab
Information Security PolicyPrivacy PolicyTerms of Service