How JWT Tokens Use Base64url Encoding

JWT tokens encode header and payload as Base64url without padding. Learn the structure, how to decode the claims, and why JWTs are not encrypted by default.

ZERO UPLOAD · ALL LOCAL
  1. Select DECODE (default) to paste Base64 and get the original text, or switch to ENCODE to convert text to Base64.
  2. For text: type or paste into the text area — the result appears instantly.
  3. For files: drop a file onto the drop zone or click it to browse — any file up to 500 MB works.
  4. Use Copy to grab the output, or Download as .txt to save long Base64 strings.
  5. Note: Base64 is encoding, not encryption. Anyone can decode it with no key.

What this page covers

  • Alphabet substitution replace - with + and _ with / before using a standard Base64 decoder
  • Padding restoration append = characters until the string length is a multiple of 4
  • Payload location split the token on '.' and take index [1]
  • Never use alg: none it tells the verifier to skip signature checking entirely, accepting forged claims
INPUT
FILE INPUT

Drop a file here

or click to select a file · any format · max 500 MB

OUTPUT

How JWT Tokens Use Base64url Encoding

A JWT payload is readable JSON wearing a Base64url coat.

JWT tokens are not encrypted by default. The header and payload are Base64url-encoded JSON, readable by anyone who can read the token. The signature section verifies integrity, not confidentiality.

A JWT has three dot-separated sections: header.payload.signature. Each section is Base64url-encoded without padding.1 Decoding the header and payload requires only atob() or any Base64url decoder , no key needed. The signature section requires the secret or public key to verify.

How JWT Base64url encoding works

JWT uses Base64url (RFC 4648 §5), not standard Base64.2 The alphabet replaces + with - and / with _ to make tokens safe in URL query strings and HTTP headers without requiring any percent-encoding step. This substitution eliminates the need to percent-encoder the token before placing it in a query parameter, which keeps the token compact and avoids double-encoding bugs.

Converting Base64url to standard Base64

Padding (=) is stripped because = has special meaning in URL query strings where it serves as the key-value separator.1 Consequently, decoding a JWT in a browser requires: replace - with +, replace _ with /, pad to a multiple of 4, then call atob(). Building on this, the decoded bytes are UTF-8 JSON that you parse with JSON.parse() to access the claims. The padding restoration step is straightforward: append = characters until the string length is a multiple of 4. A string of length 4n+2 needs two = characters; a string of length 4n+3 needs one. Most browser atob() implementations accept unpadded input when the output length is unambiguous, but explicit padding ensures compatibility across all JavaScript engines and avoids subtle decoding differences on older mobile browsers.

Restoring padding before decoding also keeps the standard decoder happy, so the same string verifies in a browser, a server, and a mobile client without per-engine special cases. It also removes a class of intermittent bugs where a payload of one length decodes cleanly while another of a different length throws on an engine that insists on exact padding.

Common pitfalls and variants

Standard Base64 libraries produce + and / characters that appear garbled when placed in a URL without percent-encoding, which is why the JWT specification explicitly requires the Base64url alphabet. Using the wrong alphabet produces tokens that are rejected by any compliant JWT library, so the choice of encoder is not a matter of preference but a requirement for interoperability.

Keeping JWT libraries consistent

Using Base64.getEncoder() in Java instead of Base64.getUrlEncoder() is a frequent bug that produces tokens that decode correctly in isolation but break URL-based flows. Stripping padding is mandatory; leaving = in the JWT breaks many parsers that expect the unpadded Base64url format. Furthermore, JWE (JSON Web Encryption) wraps a JWS with an encrypted payload; its structure is different from standard JWT. Do not assume a JWE token is readable without the decryption key. The practical impact of choosing the wrong encoder shows up when a mobile app sends a standard Base64 token to a server that expects Base64url, because the server decodes different bytes than what the app signed, producing a signature verification failure that is difficult to reproduce without inspecting the raw token bytes.

Security and best practice

Because JWT payloads are only encoded, not encrypted, never put sensitive data in JWT claims: passwords, SSNs, credit card numbers, or any value that should not be readable to anyone who holds the token. Include only claims that are safe to expose to any party. This means any intermediate proxy, CDN, or log system that captures the Authorization header can read the payload claims, so even seemingly harmless claims like email address may be sensitive depending on your privacy requirements.

Treating decoded claims as public

Verify the signature before trusting any claim, because an unverified JWT is an unauthenticated claim that could have been forged by any party. Set short expiry times (exp claim) and validate them strictly. Furthermore, use asymmetric keys (RS256 or ES256) for tokens consumed by multiple services so private-key compromise only affects signing, not verification. Never use the none algorithm in production.3 A token with the none algorithm tells the verifier to skip signature checking entirely, which means any client can forge arbitrary claims including admin privileges, and the server will accept them as valid unless the algorithm is explicitly rejected in the verification code.

Manually decoding a JWT payload in any language

Inside a JWT, the payload sits between the first and second dots. Splitting the token on . and taking index [1] gives the Base64url-encoded payload string.4 To decode it in any language: replace all - characters with + and all _ characters with /, then pad the string to a multiple-of-4 length by appending = characters. Pass the padded string to the standard Base64 decoder and parse the resulting bytes as UTF-8 JSON.

In JavaScript: JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))) (most browsers accept unpadded input for atob when the output length is unambiguous). In Python: json.loads(base64.b64decode(payload.replace('-', '+').replace('_', '/') + '=' * (-len(payload) % 4))). The character substitution and padding are necessary because the JWT specification uses Base64url without padding, while standard decoders in most languages expect the standard alphabet and padded strings.

Inspecting JWT claims in debugging workflows

During development, the browser Network panel shows raw Authorization headers for every request. Copy the bearer token value, split on ., and Base64url-decode the middle segment to read the claims without any external tool or library. Chrome DevTools Application tab also displays cookies that contain JWT values, letting you inspect token contents directly without modifying client code.

For backend debugging, structured logging that records the decoded sub, iss, and exp claims (but not refresh tokens or credential fields) lets you trace authentication failures through log search tools without exposing the full token. The exp claim is a Unix timestamp in seconds: subtract the current timestamp to calculate remaining validity. A token that expired recently may still appear to succeed during signature verification if the verification library does not enforce expiry checks automatically, which is a frequent misconfiguration in development environments.

When to use this

Use JWT for stateless session tokens in REST APIs, OAuth 2.0 access tokens,5 and service-to-service authentication where the receiver can verify the signature without calling back to the issuer. Use opaque tokens for highly sensitive sessions where payload leakage is unacceptable. For a quick look at what a token's claims actually contain, decode the JWT payload without a browser extension rather than reaching for third-party tooling you have to trust with the token.

Examples

Decode a JWT payload in JavaScript

Before
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature';
After
function decodeJwtPayload(token) {
  const payload = token.split('.')[1];
  const padded = payload.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(payload.length / 4) * 4, '=');
  return JSON.parse(atob(padded));
}

This decodes the payload without verifying the signature. Always verify signatures server-side before trusting claims.

Decode a JWT payload in Python

Before
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature'
After
import base64, json
def decode_jwt_payload(token):
    payload = token.split('.')[1]
    padded = payload + '=' * (-len(payload) % 4)
    decoded = base64.b64decode(padded.replace('-', '+').replace('_', '/'))
    return json.loads(decoded)

PyJWT and similar libraries handle this correctly. Use them in production rather than manual decoding.

Sources
  1. 1.

    M. Jones, J. Bradley, and N. Sakimura, "JSON Web Token (JWT)," RFC 7519, IETF, May 2015. https://www.rfc-editor.org/rfc/rfc7519

  2. 2.

    S. Josefsson and I. Williams, "The Base16, Base32, and Base64 Data Encodings," RFC 4648, IETF, October 2006. https://www.rfc-editor.org/rfc/rfc4648

  3. 3.

    M. Jones, "JSON Web Algorithms (JWA)," RFC 7518, IETF, May 2015. https://datatracker.ietf.org/doc/html/rfc7518

  4. 4.

    "JSON Web Token," Wikipedia, accessed June 2026. https://en.wikipedia.org/wiki/JSON_Web_Token

  5. 5.

    D. Hardt, "The OAuth 2.0 Authorization Framework," RFC 6749, IETF, October 2012. https://datatracker.ietf.org/doc/html/rfc6749

FAQ