Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Security

Tokens

What is a token, and what does the server look at to trust it?

When a login succeeds, the server writes down "this person is kim" as text, attaches a signature calculated with a secret key that only the server knows, and gives it to the client. This text is called a token, and the most common format is the JWT. A token is three pieces joined by dots (.): the header, the payload, and the signature. The client holds the token and sends it in the Authorization: Bearer token header with every request. The server does not keep a separate list of logged-in people. Instead, it recalculates the signature from the header and the payload of the token it received, and checks whether it is the same as the signature that came with the token. If it is the same, the server trusts that the token was made by the server and has not been changed, and reads the payload. In this lab, you change the payload of the token, remove the signature, sign with a different key, or take the token away, and also make the server skip the signature check, and you see whom the server trusts and what response it gives. The code panel shows the request the client sends and the token decoded, and it is not Java code. The signature is long, so it is written as (signature). The server in this lab is built to answer a GET /me request with "Hello, user name", and its server code is not shown on the screen.

Checked against real execution · Sent 10 combinations of tokens as real HTTP requests to an http server on Node.js 24.18.0 and checked against the results. The implementation that makes the tokens was checked against the example in RFC 7515 Appendix A.1 and the verification of the JWT library jose4j 0.9.5

Concepts in this lab

Token authentication
Token authentication is a method in which the server makes a token for a user whose login succeeded, the client sends that token with every request, and the server checks it. The token has "this person is kim" written in it as text, and a signature calculated with a secret key that only the server knows is attached. The server recalculates the signature of the token it received, and if it matches, it trusts the payload, so it does not need to keep a separate list of logged-in people. This is different from the session method, where the server keeps a list and gives the client only a number. Anyone who holds the token can make requests with it (the Bearer token of RFC 6750 section 1.2), and the payload is not encrypted, so anyone can read it.
JSON Web Token (JWT)
A token that carries content such as user information (claims) and has a signature attached. It has three parts: a header, a payload, and a signature. The server verifies the signature to confirm that the token was not forged, and it does not need to look up a separate store. The payload is encoded, not encrypted, so anyone can read its contents. Do not put sensitive information in it.
Signature (token)
The signature of a token is a value calculated from the header and the payload of the token with a secret key that only the server knows, and it is attached at the very end of the token. In the HS256 method of a JWT, the text of the base64url header and payload joined with a dot (.) is calculated with HMAC-SHA256 (RFC 7515 section 2, Appendix A.1). The server does the same calculation again from the header and the payload of the token it received and compares it with the signature that came with it, and if even one character of the payload changed, the calculated value differs and does not match. The premise is that a party that does not know the key cannot make a matching signature, so only the server should know the key. A signature vouches that the payload has not changed, but it does not hide the payload.
Authorization: Bearer
Authorization is an HTTP request header that carries credentials in a request, and Bearer is the name of the method that tells the credentials are a token. The client sends the token to the server by writing one line of the form Authorization: Bearer token (RFC 6750 section 2.1). A Bearer token means a token that anyone who holds it can use (section 1.2). If the request has no such header or the token cannot be accepted, the server usually returns a 401 response, and it is defined that the name of the method, Bearer, is then written in the WWW-Authenticate header (section 3).

Try these

Running with the default settings

  1. The client sends a GET /me request to the server. The Authorization header carries the token after Bearer. The server made this token when the login succeeded, and the client keeps it as it is and attaches it to the request.
  2. The server takes the token from the Authorization header and splits it at the dots (.). From the front, they are the header, the payload, and the signature. The header and the payload are not encrypted. They are text written in a notation called base64url, so even without a key, if you decode them, the header reads as {"alg":"HS256"} and the payload as {"user":"kim"}. Being able to read it does not mean you trust it.
  3. The server recalculates the signature with its own secret key, using the header and the payload of the token it received. Only the server knows the key, and it was never given to the client. The calculated value is jzV9172X….
  4. The server compares the value it calculated (jzV9172X…) with the signature that came with the token (jzV9172X…). They are the same. This token has a signature calculated with the server's key, which means the header and the payload have not changed by even one character since the signature was made.
  5. The signature matches, so the server trusts this token as one that it made and that has not been changed, and reads the payload. The user is kim. The server did not write anywhere that kim logged in. That fact is written in the token, and the signature vouches for it. This server code has no list of logged-in people. The server handles this request as a request from kim and makes a 200 response.
  6. The client receives a 200 response. The body is "Hello, kim".

Result of this run

Code

GET /me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoia2ltIn0.(signature)

# Split the token at the dots (.) and decode it
header: {"alg":"HS256"}
payload: {"user":"kim"}
signature: (value calculated from the header and payload with the server's secret key)

# Server
signature check: yes

Assumptions of this model

The server is a single one started with the http module of Node.js 24.18.0 on a free port of 127.0.0.1. POST /login makes a token from the name it receives and returns it (the password check is left out and the server stores nothing). GET /me takes the token after Bearer from the Authorization header and splits it at the dots. If signature verification is on, it recalculates HMAC-SHA256 from the header and the payload and compares it with the signature that came with the token (if it does not match or is empty, the result is 401). If verification is off, it does not compare, reads the user in the payload, and answers with 200 and "Hello, user name". This decision is made by one function that reads only the request, the key, and whether verification is on, and it does not look up any storage such as a list of logged-in people. The client sent real HTTP requests with Node.js http.request, and in all 10 combinations we checked the first line of the request that the server received (GET /me, HTTP/1.1), the Authorization header, the result of the signature check, and the response status and body, and checked them against the model. The token line in the code panel is the same text as the header the server received, with only the signature part shortened to (signature), and the decoded header and payload are the same text as the token the server received decoded with base64url. The request the server received also had other headers such as Host and Connection, and they are shortened on the screen. The localhost on the screen was not used, and the request went to another port on the PC used to write this lab. That the implementation that makes the tokens follows the standard was checked first by running the same calculation on the example in RFC 7515 Appendix A.1 (the key, header, and payload written in the document) and getting the same value as the signature in the document. We also verified the same tokens with an HS256 key with jose4j 0.9.5, a JWT library in the Gradle cache on the PC used to write this lab. Only the token the server gave was valid, and the one with the payload changed, the one with the signature removed, and the one signed with a different key were not valid, which was the same as the decision of this server. Also, when we sent the token the server gave to a new server (with the same key) that had never received a login, it accepted it with 200, and a server with a different key rejected the same token. When the payload was changed to admin, the value the server recalculated from that payload differed from the signature that came with the token, and the signature made with a different key also differed from the value the server calculated. The token with the signature removed was sent in the form "header.payload.", with the dot left and only the place for the signature emptied. We separately saw that "header.payload", with the dot removed too, becomes 401 as a malformed token on this server whether or not signature verification is on, but this comes from the way this server was built, so it is not used on the screen. The 401 response had a WWW-Authenticate: Bearer header, and it is shortened on the screen. This lab deals only with a token with the signature part emptied, and does not deal with the attack of changing alg in the header to none. This server does not read alg in the header and always verifies with HMAC-SHA256, and the problems around algorithms are covered in the in-depth lab "JWT structure and verification". It also does not cover the expiration time of a token (exp), problems when a token is stolen, the difficulty of invalidating a token that was already given, where to keep a token, and the public-key method (RS256). What we opened in the documents and checked is as follows. RFC 7519 sections 1 and 3 (a JWT represents claims as a JSON object in a JWS or JWE structure, and in a signed JWT the claims set is the payload of the JWS), the first step of section 7.2 (a JWT must contain at least one dot, and what is before the first dot is the header), RFC 7515 section 2 (the signing input is the base64url header, a dot, and the base64url payload), section 3 (JWS overview), section 5.2 (signature verification is done on the signing input, and if no verification succeeds, the JWS is invalid), section 7.1 (the compact form joins the header, the payload, and the signature with dots), Appendix A.1 (the HMAC SHA-256 example), RFC 6750 section 1.2 (a Bearer token is a token that anyone who holds it can use), section 2.1 (the token is sent in the Authorization header with the Bearer method), and section 3 (the WWW-Authenticate response header, with the method name Bearer). RFC 6750 is a document for OAuth 2.0 access tokens, but the token in this lab was also sent in a header of the same form. The key and the tokens on the screen are values made for this lab, and the value of the key is not shown on the screen. We did not run it in a browser.

Previous lab: Sessions · Next lab: Hashing

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these