JWTs: Encrypted? JSON? Client-Side Use? (.NET + React)
TL;DR Standard JWTs are signed (JWS) not encrypted; the header & payload are Base64Url-encoded JSON. Use HTTPS for transport confidentiality, keep sensitive data out of the payload, and protect APIs with [Authorize]. React can decode claims to show/hide UI, but the server enforces security.
Are JWTs Encrypted?
No — not by default. A typical JWT is a signed token (JWS), which guarantees integrity (no tampering) but does not hide the payload. Anyone holding the token can Base64-decode its header and payload.
| Concept | What it provides | Visible to client? |
|---|---|---|
| JWS (signed JWT) | Authenticity & integrity (valid signature, issuer, audience, expiry) | Yes (payload readable) |
| JWE (encrypted JWT) | Confidentiality (payload is unreadable without key) | No (payload hidden) |
| HTTPS/TLS | Encrypts data in transit end-to-end | N/A (transport layer) |
JWT Structure
A JWT has three Base64Url-encoded parts separated by dots:
<header>.<payload>.<signature>
Example (truncated):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxIiwiZW1haWwiOiJ0cmFjZXlAa2F0bWFpZC5jby51ayIsInJvbGUiOiJBZG1pbiIsImV4cCI6MTcyOTgwMTEyMn0.
hKJ6E5qH6x4PV9FyT8h5xUIY5F4gQwF6Nq4Zx1Bih8I
Header JSON
{
"alg": "HS256",
"typ": "JWT"
}
Payload JSON
{
"sub": "1",
"email": "bob@example.com",
"role": "Admin",
"exp": 1729801122
}
Are JWTs JSON Once Decoded?
Yes. The header and payload are literally JSON documents after Base64Url decoding. The third part is a binary signature (also Base64Url-encoded).
Decode in the Browser (demo)
<script>
function base64UrlToJson(str) {
const b64 = str.replace(/-/g, '+').replace(/_/g, '/');
const json = atob(b64);
return JSON.parse(json);
}
const token = "<your.jwt.here>";
const [h, p] = token.split('.');
console.log("Header:", base64UrlToJson(h));
console.log("Payload:", base64UrlToJson(p));
</script>
How React Uses the Token
React doesn’t enforce security—that’s the API’s job—but it can decode claims to control UI and routes:
// Show/hide UI based on role
import jwtDecode from "jwt-decode";
const token = localStorage.getItem("katmaid_token");
const user = token ? jwtDecode(token) : null;
{user?.role === "Admin" && <NavLink to="/admin">Admin Panel</NavLink>}
// Basic route guard
import { Navigate } from "react-router-dom";
function Protected({ children }) {
const t = localStorage.getItem("katmaid_token");
if (!t) return <Navigate to="/login" replace />;
const u = jwtDecode(t);
if (u.exp * 1000 < Date.now()) return <Navigate to="/login" replace />;
return children;
}
ASP.NET Core: Validation Recap
Once you configure AddAuthentication().AddJwtBearer(...) and add UseAuthentication()/UseAuthorization(), attributes do the rest:
// Anyone with a valid token
[Authorize]
public IActionResult Get() => Ok();
// Only users whose token contains role "Admin"
[Authorize(Roles = "Admin")]
public IActionResult Update(...) => Ok();
// Public
[AllowAnonymous]
public IActionResult Login(...) => Ok();
What If I Want Encryption Too (JWE)?
ASP.NET Core doesn’t do JWE out of the box. You’d sign (JWS) and then encrypt (JWE) with a library (e.g. Jose.JWT), and add custom decryption before validation. Usually HTTPS + JWS is enough.
// Jose.JWT example (symmetric encryption)
using Jose;
using System.Text;
var payload = new { sub = "1", email = "bob@example.com" };
var key = Encoding.UTF8.GetBytes("SuperSecretEncryptionKey123!5#");
// Encrypt (A256GCM)
string jwe = JWT.Encode(payload, key, JweAlgorithm.DIR, JweEncryption.A256GCM);
// Decrypt
var json = JWT.Decode(jwe, key);
Best-Practice Checklist
✔ Use HTTPS everywhere
✔ Keep JWTs minimal; no secrets in payload
✔ Validate issuer, audience, signature, lifetime
✔ Set short expiries (e.g., 15–120 min) + refresh flow if needed
✔ Store signing keys securely (Key Vault / env vars); rotate
✔ Use role/permission claims for authz on the server
✔ In SPAs, protect routes/UI but always re-check on the server
✔ Prefer HttpOnly cookies to reduce XSS token theft (trade-offs apply)

















