From c1217a44f4f5aa66364daddd46e3b60bc5696520 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:49:26 +0000 Subject: [PATCH 1/5] Initial plan From 8e8057b5adbe1e50becd7eebbc50c97b933240c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:57:49 +0000 Subject: [PATCH 2/5] Add JWT section to Authentication Cheat Sheet and rename JWT Java Cheat Sheet - Add 'JSON Web Tokens (JWT)' section to Authentication Cheat Sheet with common implementation mistakes (storage, expiration, alg:none, claim validation, payload data, weak secrets, revocation) - Rename JSON_Web_Token_for_Java_Cheat_Sheet.md to JSON_Web_Token_Cheat_Sheet.md and update Objective section to note Java examples are language-neutral - Update all cross-references across Index.md, IndexASVS.md, IndexASVS4.md, IndexMASVS.md, IndexProactiveControls.md, IndexTopTen.md, Forgot_Password_Cheat_Sheet.md, REST_Security_Cheat_Sheet.md Closes #1973 Closes #1176 Co-authored-by: mackowski <35339942+mackowski@users.noreply.github.com> Agent-Logs-Url: https://github.com/OWASP/CheatSheetSeries/sessions/a9a54bd6-cdc5-4d96-86db-c4c2583cfe89 --- Index.md | 166 +++++++++++------- IndexASVS.md | 2 +- IndexASVS4.md | 2 +- IndexMASVS.md | 2 +- IndexProactiveControls.md | 2 +- IndexTopTen.md | 2 +- cheatsheets/Authentication_Cheat_Sheet.md | 26 +++ cheatsheets/Forgot_Password_Cheat_Sheet.md | 2 +- ...Sheet.md => JSON_Web_Token_Cheat_Sheet.md} | 6 +- cheatsheets/REST_Security_Cheat_Sheet.md | 2 +- 10 files changed, 139 insertions(+), 73 deletions(-) rename cheatsheets/{JSON_Web_Token_for_Java_Cheat_Sheet.md => JSON_Web_Token_Cheat_Sheet.md} (98%) diff --git a/Index.md b/Index.md index cfb1c4efda..0f6ae55e82 100644 --- a/Index.md +++ b/Index.md @@ -1,70 +1,80 @@ # Index Alphabetical -**91** cheat sheets available. +**112** cheat sheets available. *Icons beside the cheat sheet name indicate in which language(s) code snippet(s) are provided.* -[A](Index.md#a) [B](Index.md#b) [C](Index.md#c) [D](Index.md#d) [E](Index.md#e) [F](Index.md#f) [G](Index.md#g) [H](Index.md#h) [I](Index.md#i) [J](Index.md#j) [K](Index.md#k) [L](Index.md#l) [M](Index.md#m) [N](Index.md#n) [O](Index.md#o) [P](Index.md#p) [Q](Index.md#q) [R](Index.md#r) [S](Index.md#s) [T](Index.md#t) [U](Index.md#u) [V](Index.md#v) [W](Index.md#w) [X](Index.md#x) +[A](Index.md#a) [B](Index.md#b) [C](Index.md#c) [D](Index.md#d) [E](Index.md#e) [F](Index.md#f) [G](Index.md#g) [H](Index.md#h) [I](Index.md#i) [J](Index.md#j) [K](Index.md#k) [L](Index.md#l) [M](Index.md#m) [N](Index.md#n) [O](Index.md#o) [P](Index.md#p) [Q](Index.md#q) [R](Index.md#r) [S](Index.md#s) [T](Index.md#t) [U](Index.md#u) [V](Index.md#v) [W](Index.md#w) [X](Index.md#x) [Z](Index.md#z) ## A -[Access Control Cheat Sheet](cheatsheets/Access_Control_Cheat_Sheet.md) +[Authentication Cheat Sheet](cheatsheets/Authentication_Cheat_Sheet.md) + +[Abuse Case Cheat Sheet](cheatsheets/Abuse_Case_Cheat_Sheet.md) [Attack Surface Analysis Cheat Sheet](cheatsheets/Attack_Surface_Analysis_Cheat_Sheet.md) -[Authentication Cheat Sheet](cheatsheets/Authentication_Cheat_Sheet.md) +[Authorization Testing Automation Cheat Sheet](cheatsheets/Authorization_Testing_Automation_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) -[Authorization Cheat Sheet](cheatsheets/Authorization_Cheat_Sheet.md) +[AI Agent Security Cheat Sheet](cheatsheets/AI_Agent_Security_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) -[AJAX Security Cheat Sheet](cheatsheets/AJAX_Security_Cheat_Sheet.md) ![Json](assets/Index_Json.svg) +[AJAX Security Cheat Sheet](cheatsheets/AJAX_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Json](assets/Index_Json.svg) -[Abuse Case Cheat Sheet](cheatsheets/Abuse_Case_Cheat_Sheet.md) +[Automotive Security Cheat Sheet](cheatsheets/Automotive_Security_Cheat_Sheet.md) -[Authorization Testing Automation Cheat Sheet](cheatsheets/Authorization_Testing_Automation_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) +[Access Control Cheat Sheet](cheatsheets/Access_Control_Cheat_Sheet.md) + +[Authorization Cheat Sheet](cheatsheets/Authorization_Cheat_Sheet.md) ## B +[Browser Extension Vulnerabilities Cheat Sheet](cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Json](assets/Index_Json.svg) + [Bean Validation Cheat Sheet](cheatsheets/Bean_Validation_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) ## C -[CI CD Security Cheat Sheet](cheatsheets/CI_CD_Security_Cheat_Sheet.md) +[C-Based Toolchain Hardening Cheat Sheet](cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.md) ![C](assets/Index_C.svg) ![Bash](assets/Index_Bash.svg) -[Cross-Site Request Forgery Prevention Cheat Sheet](cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) +[Cookie Theft Mitigation Cheat Sheet](cheatsheets/Cookie_Theft_Mitigation_Cheat_Sheet.md) -[Clickjacking Defense Cheat Sheet](cheatsheets/Clickjacking_Defense_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) +[Content Security Policy Cheat Sheet](cheatsheets/Content_Security_Policy_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[Cross Site Scripting Prevention Cheat Sheet](cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) +[Clickjacking Defense Cheat Sheet](cheatsheets/Clickjacking_Defense_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[C-Based Toolchain Hardening Cheat Sheet](cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.md) ![C](assets/Index_C.svg) ![Bash](assets/Index_Bash.svg) +[Cross-Site Request Forgery Prevention Cheat Sheet](cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[Choosing and Using Security Questions Cheat Sheet](cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.md) +[Cross Site Scripting Prevention Cheat Sheet](cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) -[Content Security Policy Cheat Sheet](cheatsheets/Content_Security_Policy_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) +[Cryptographic Storage Cheat Sheet](cheatsheets/Cryptographic_Storage_Cheat_Sheet.md) [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) -[Cryptographic Storage Cheat Sheet](cheatsheets/Cryptographic_Storage_Cheat_Sheet.md) +[CI CD Security Cheat Sheet](cheatsheets/CI_CD_Security_Cheat_Sheet.md) + +[Choosing and Using Security Questions Cheat Sheet](cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.md) ## D -[Deserialization Cheat Sheet](cheatsheets/Deserialization_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Python](assets/Index_Python.svg) +[DotNet Security Cheat Sheet](cheatsheets/DotNet_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Csharp](assets/Index_Csharp.svg) ![Html](assets/Index_Html.svg) ![Xml](assets/Index_Xml.svg) -[Docker Security Cheat Sheet](cheatsheets/Docker_Security_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) +[Database Security Cheat Sheet](cheatsheets/Database_Security_Cheat_Sheet.md) -[Django Security Cheat Sheet](cheatsheets/Django_Security_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Python](assets/Index_Python.svg) +[DOM based XSS Prevention Cheat Sheet](cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[Django REST Framework Cheat Sheet](cheatsheets/Django_REST_Framework_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) +[DOM Clobbering Prevention Cheat Sheet](cheatsheets/DOM_Clobbering_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[Database Security Cheat Sheet](cheatsheets/Database_Security_Cheat_Sheet.md) +[Denial of Service Cheat Sheet](cheatsheets/Denial_of_Service_Cheat_Sheet.md) -[DotNet Security Cheat Sheet](cheatsheets/DotNet_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Csharp](assets/Index_Csharp.svg) ![Html](assets/Index_Html.svg) ![Xml](assets/Index_Xml.svg) +[Docker Security Cheat Sheet](cheatsheets/Docker_Security_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) -[DOM based XSS Prevention Cheat Sheet](cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) +[Django Security Cheat Sheet](cheatsheets/Django_Security_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Python](assets/Index_Python.svg) -[Denial of Service Cheat Sheet](cheatsheets/Denial_of_Service_Cheat_Sheet.md) +[Deserialization Cheat Sheet](cheatsheets/Deserialization_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Python](assets/Index_Python.svg) -[DOM Clobbering Prevention Cheat Sheet](cheatsheets/DOM_Clobbering_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) +[Dependency Graph SBOM Cheat Sheet](cheatsheets/Dependency_Graph_SBOM_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) + +[Django REST Framework Cheat Sheet](cheatsheets/Django_REST_Framework_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) [Drone Security Cheat Sheet](cheatsheets/Drone_Security_Cheat_Sheet.md) @@ -82,33 +92,35 @@ [GraphQL Cheat Sheet](cheatsheets/GraphQL_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) +[gRPC Security Cheat Sheet](cheatsheets/gRPC_Security_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) + ## H -[HTTP Headers Cheat Sheet](cheatsheets/HTTP_Headers_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Xml](assets/Index_Xml.svg) ![Php](assets/Index_Php.svg) +[HTTP Strict Transport Security Cheat Sheet](cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md) -[HTML5 Security Cheat Sheet](cheatsheets/HTML5_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Html](assets/Index_Html.svg) ![Json](assets/Index_Json.svg) ![Shell](assets/Index_Shell.svg) +[HTTP Headers Cheat Sheet](cheatsheets/HTTP_Headers_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Xml](assets/Index_Xml.svg) ![Php](assets/Index_Php.svg) -[HTTP Strict Transport Security Cheat Sheet](cheatsheets/HTTP_Strict_Transport_Security_Cheat_Sheet.md) +[HTML5 Security Cheat Sheet](cheatsheets/HTML5_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) ## I -[Injection Prevention Cheat Sheet](cheatsheets/Injection_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) - -[Injection Prevention in Java Cheat Sheet](cheatsheets/Injection_Prevention_in_Java_Cheat_Sheet.md) +[Insecure Direct Object Reference Prevention Cheat Sheet](cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.md) [Input Validation Cheat Sheet](cheatsheets/Input_Validation_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) [Infrastructure as Code Security Cheat Sheet](cheatsheets/Infrastructure_as_Code_Security_Cheat_Sheet.md) -[Insecure Direct Object Reference Prevention Cheat Sheet](cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.md) +[Injection Prevention in Java Cheat Sheet](cheatsheets/Injection_Prevention_in_Java_Cheat_Sheet.md) -## J +[Injection Prevention Cheat Sheet](cheatsheets/Injection_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) -[Java Security Cheat Sheet](cheatsheets/Java_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) +## J [JAAS Cheat Sheet](cheatsheets/JAAS_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) -[JSON Web Token for Java Cheat Sheet](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Json](assets/Index_Json.svg) ![Sql](assets/Index_Sql.svg) +[Java Security Cheat Sheet](cheatsheets/Java_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) + +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Json](assets/Index_Json.svg) ![Sql](assets/Index_Sql.svg) ## K @@ -120,11 +132,15 @@ [Logging Cheat Sheet](cheatsheets/Logging_Cheat_Sheet.md) -[Laravel Cheat Sheet](cheatsheets/Laravel_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Php](assets/Index_Php.svg) ![Sql](assets/Index_Sql.svg) ![Bash](assets/Index_Bash.svg) +[Legacy Application Management Cheat Sheet](cheatsheets/Legacy_Application_Management_Cheat_Sheet.md) + +[Logging Vocabulary Cheat Sheet](cheatsheets/Logging_Vocabulary_Cheat_Sheet.md) ![Json](assets/Index_Json.svg) -[LDAP Injection Prevention Cheat Sheet](cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md) +[LDAP Injection Prevention Cheat Sheet](cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) -[Logging Vocabulary Cheat Sheet](cheatsheets/Logging_Vocabulary_Cheat_Sheet.md) +[Laravel Cheat Sheet](cheatsheets/Laravel_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Php](assets/Index_Php.svg) ![Sql](assets/Index_Sql.svg) ![Bash](assets/Index_Bash.svg) + +[LLM Prompt Injection Prevention Cheat Sheet](cheatsheets/LLM_Prompt_Injection_Prevention_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) ## M @@ -134,20 +150,26 @@ [Multifactor Authentication Cheat Sheet](cheatsheets/Multifactor_Authentication_Cheat_Sheet.md) -[Mass Assignment Cheat Sheet](cheatsheets/Mass_Assignment_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Html](assets/Index_Html.svg) ![Php](assets/Index_Php.svg) +[Multi Tenant Security Cheat Sheet](cheatsheets/Multi_Tenant_Security_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) ![Sql](assets/Index_Sql.svg) [Microservices based Security Arch Doc Cheat Sheet](cheatsheets/Microservices_based_Security_Arch_Doc_Cheat_Sheet.md) +[MCP Security Cheat Sheet](cheatsheets/MCP_Security_Cheat_Sheet.md) + +[Mass Assignment Cheat Sheet](cheatsheets/Mass_Assignment_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Html](assets/Index_Html.svg) ![Php](assets/Index_Php.svg) + ## N -[NodeJS Docker Cheat Sheet](cheatsheets/NodeJS_Docker_Cheat_Sheet.md) +[NoSQL Security Cheat Sheet](cheatsheets/NoSQL_Security_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) -[NPM Security Cheat Sheet](cheatsheets/NPM_Security_Cheat_Sheet.md) +[NodeJS Docker Cheat Sheet](cheatsheets/NodeJS_Docker_Cheat_Sheet.md) -[Nodejs Security Cheat Sheet](cheatsheets/Nodejs_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Bash](assets/Index_Bash.svg) +[NPM Security Cheat Sheet](cheatsheets/NPM_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Json](assets/Index_Json.svg) ![Bash](assets/Index_Bash.svg) [Network Segmentation Cheat Sheet](cheatsheets/Network_Segmentation_Cheat_Sheet.md) +[Nodejs Security Cheat Sheet](cheatsheets/Nodejs_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Bash](assets/Index_Bash.svg) + ## O [OAuth2 Cheat Sheet](cheatsheets/OAuth2_Cheat_Sheet.md) @@ -156,10 +178,10 @@ ## P -[Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) - [PHP Configuration Cheat Sheet](cheatsheets/PHP_Configuration_Cheat_Sheet.md) +[Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) + [Pinning Cheat Sheet](cheatsheets/Pinning_Cheat_Sheet.md) [Prototype Pollution Prevention Cheat Sheet](cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) @@ -170,48 +192,60 @@ ## R -[REST Security Cheat Sheet](cheatsheets/REST_Security_Cheat_Sheet.md) - [REST Assessment Cheat Sheet](cheatsheets/REST_Assessment_Cheat_Sheet.md) [Ruby on Rails Cheat Sheet](cheatsheets/Ruby_on_Rails_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Ruby](assets/Index_Ruby.svg) ![Bash](assets/Index_Bash.svg) -## S +[REST Security Cheat Sheet](cheatsheets/REST_Security_Cheat_Sheet.md) -[Secure Product Design Cheat Sheet](cheatsheets/Secure_Product_Design_Cheat_Sheet.md) +## S [Secure Cloud Architecture Cheat Sheet](cheatsheets/Secure_Cloud_Architecture_Cheat_Sheet.md) -[Securing Cascading Style Sheets Cheat Sheet](cheatsheets/Securing_Cascading_Style_Sheets_Cheat_Sheet.md) +[SQL Injection Prevention Cheat Sheet](cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Vbnet](assets/Index_Vbnet.svg) -[Security Terminology Cheat Sheet](cheatsheets/Security_Terminology_Cheat_Sheet.md) +[Secure Product Design Cheat Sheet](cheatsheets/Secure_Product_Design_Cheat_Sheet.md) -[SQL Injection Prevention Cheat Sheet](cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Vbnet](assets/Index_Vbnet.svg) +[Session Management Cheat Sheet](cheatsheets/Session_Management_Cheat_Sheet.md) + +[Secure Code Review Cheat Sheet](cheatsheets/Secure_Code_Review_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) [Server Side Request Forgery Prevention Cheat Sheet](cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Python](assets/Index_Python.svg) ![Ruby](assets/Index_Ruby.svg) ![Bash](assets/Index_Bash.svg) +[Serverless FaaS Security Cheat Sheet](cheatsheets/Serverless_FaaS_Security_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) ![Json](assets/Index_Json.svg) ![Bash](assets/Index_Bash.svg) + +[Symfony Cheat Sheet](cheatsheets/Symfony_Cheat_Sheet.md) ![Php](assets/Index_Php.svg) ![Bash](assets/Index_Bash.svg) + +[Securing Cascading Style Sheets Cheat Sheet](cheatsheets/Securing_Cascading_Style_Sheets_Cheat_Sheet.md) + [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) -[Session Management Cheat Sheet](cheatsheets/Session_Management_Cheat_Sheet.md) +[Secrets Management Cheat Sheet](cheatsheets/Secrets_Management_Cheat_Sheet.md) ![Python](assets/Index_Python.svg) -[Secrets Management Cheat Sheet](cheatsheets/Secrets_Management_Cheat_Sheet.md) +[Software Supply Chain Security Cheat Sheet](cheatsheets/Software_Supply_Chain_Security_Cheat_Sheet.md) -[Symfony Cheat Sheet](cheatsheets/Symfony_Cheat_Sheet.md) ![Php](assets/Index_Php.svg) ![Bash](assets/Index_Bash.svg) +[Subdomain Takeover Prevention Cheat Sheet](cheatsheets/Subdomain_Takeover_Prevention_Cheat_Sheet.md) -## T +[Security Terminology Cheat Sheet](cheatsheets/Security_Terminology_Cheat_Sheet.md) -[Transaction Authorization Cheat Sheet](cheatsheets/Transaction_Authorization_Cheat_Sheet.md) +[Secure AI Model Ops Cheat Sheet](cheatsheets/Secure_AI_Model_Ops_Cheat_Sheet.md) -[TLS Cipher String Cheat Sheet](cheatsheets/TLS_Cipher_String_Cheat_Sheet.md) +## T + +[Third Party Javascript Management Cheat Sheet](cheatsheets/Third_Party_Javascript_Management_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) -[Transport Layer Security Cheat Sheet](cheatsheets/Transport_Layer_Security_Cheat_Sheet.md) ![Bash](assets/Index_Bash.svg) +[Transport Layer Security Cheat Sheet](cheatsheets/Transport_Layer_Security_Cheat_Sheet.md) [Transport Layer Protection Cheat Sheet](cheatsheets/Transport_Layer_Protection_Cheat_Sheet.md) -[Third Party Javascript Management Cheat Sheet](cheatsheets/Third_Party_Javascript_Management_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) +[TLS Cipher String Cheat Sheet](cheatsheets/TLS_Cipher_String_Cheat_Sheet.md) + +[Third Party Payment Gateway Integration Cheat Sheet](cheatsheets/Third_Party_Payment_Gateway_Integration_Cheat_Sheet.md) [Threat Modeling Cheat Sheet](cheatsheets/Threat_Modeling_Cheat_Sheet.md) +[Transaction Authorization Cheat Sheet](cheatsheets/Transaction_Authorization_Cheat_Sheet.md) + ## U [User Privacy Protection Cheat Sheet](cheatsheets/User_Privacy_Protection_Cheat_Sheet.md) @@ -220,22 +254,28 @@ ## V -[Virtual Patching Cheat Sheet](cheatsheets/Virtual_Patching_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) - [Vulnerability Disclosure Cheat Sheet](cheatsheets/Vulnerability_Disclosure_Cheat_Sheet.md) [Vulnerable Dependency Management Cheat Sheet](cheatsheets/Vulnerable_Dependency_Management_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) +[Virtual Patching Cheat Sheet](cheatsheets/Virtual_Patching_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) + ## W +[WebSocket Security Cheat Sheet](cheatsheets/WebSocket_Security_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) + [Web Service Security Cheat Sheet](cheatsheets/Web_Service_Security_Cheat_Sheet.md) ## X -[XML External Entity Prevention Cheat Sheet](cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Cpp](assets/Index_Cpp.svg) ![Php](assets/Index_Php.svg) +[XML Security Cheat Sheet](cheatsheets/XML_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) ![Bash](assets/Index_Bash.svg) [XSS Filter Evasion Cheat Sheet](cheatsheets/XSS_Filter_Evasion_Cheat_Sheet.md) ![Html](assets/Index_Html.svg) ![Php](assets/Index_Php.svg) -[XML Security Cheat Sheet](cheatsheets/XML_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) ![Bash](assets/Index_Bash.svg) +[XML External Entity Prevention Cheat Sheet](cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Csharp](assets/Index_Csharp.svg) ![Cpp](assets/Index_Cpp.svg) ![Php](assets/Index_Php.svg) [XS Leaks Cheat Sheet](cheatsheets/XS_Leaks_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Html](assets/Index_Html.svg) + +## Z + +[Zero Trust Architecture Cheat Sheet](cheatsheets/Zero_Trust_Architecture_Cheat_Sheet.md) diff --git a/IndexASVS.md b/IndexASVS.md index 6f08d315c3..ad406ccf35 100644 --- a/IndexASVS.md +++ b/IndexASVS.md @@ -425,7 +425,7 @@ None. ### V9.1 Token source and integrity -[JSON Web Token Cheat Sheet for Java](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/IndexASVS4.md b/IndexASVS4.md index 422c21cf27..e52e9bde31 100644 --- a/IndexASVS4.md +++ b/IndexASVS4.md @@ -241,7 +241,7 @@ None. ### V3.5 Token-based Session Management -[JSON Web Token Cheat Sheet for Java](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [REST Security Cheat Sheet](cheatsheets/REST_Security_Cheat_Sheet.md) diff --git a/IndexMASVS.md b/IndexMASVS.md index 3a1834a1d2..3868232221 100644 --- a/IndexMASVS.md +++ b/IndexMASVS.md @@ -46,7 +46,7 @@ This index is based on version [2.1.0](https://github.com/OWASP/owasp-masvs/rele [Access Control Cheat Sheet](cheatsheets/Access_Control_Cheat_Sheet.md) -[JSON Web Token Cheat Sheet for Java](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) diff --git a/IndexProactiveControls.md b/IndexProactiveControls.md index c5b479cfa2..31eca98c08 100644 --- a/IndexProactiveControls.md +++ b/IndexProactiveControls.md @@ -104,7 +104,7 @@ This cheat sheet will help users of the [OWASP Top Ten Proactive Controls 2018]( [JAAS Cheat Sheet](cheatsheets/JAAS_Cheat_Sheet.md) -[JSON Web Token Cheat Sheet for Java](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) diff --git a/IndexTopTen.md b/IndexTopTen.md index ea83737c86..fb9058f52f 100644 --- a/IndexTopTen.md +++ b/IndexTopTen.md @@ -60,7 +60,7 @@ This cheat sheet will help users of the [OWASP Top Ten](https://owasp.org/Top10/ - [Choosing and Using Security Questions Cheat Sheet](cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.md) - [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) - [Denial of Service Cheat Sheet](cheatsheets/Denial_of_Service_Cheat_Sheet.md) -- [JSON Web Token for Java Cheat Sheet](cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md) +- [JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) - [Multifactor Authentication Cheat Sheet](cheatsheets/Multifactor_Authentication_Cheat_Sheet.md) - [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) - [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/cheatsheets/Authentication_Cheat_Sheet.md b/cheatsheets/Authentication_Cheat_Sheet.md index 777b2b0aa7..e6de83debb 100644 --- a/cheatsheets/Authentication_Cheat_Sheet.md +++ b/cheatsheets/Authentication_Cheat_Sheet.md @@ -343,6 +343,32 @@ U2F augments password-based authentication using a hardware token (typically USB **FIDO2**: FIDO2 and WebAuthn, encompassing previous standards (UAF/U2F), form the foundation of modern **Passkeys** technology. Passkeys enable users to securely log in using local user verification (such as biometrics or device PINs) and often supporting cloud synchronization across devices. This technology is widely supported by major platforms. (Windows Hello/Mac Touch ID) +### JSON Web Tokens (JWT) + +JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are a compact, URL-safe way to represent claims between two parties. They are widely used as bearer tokens in authentication and authorization flows, including as OIDC ID tokens and OAuth 2.0 access tokens. + +#### When to Use (and Not Use) JWTs + +JWTs are well-suited for stateless authentication across distributed systems and for interoperating with standards such as OIDC and OAuth 2.0. However, they are not always the right choice: + +- **Use** JWTs when stateless, cross-service token passing is required or when following standards like OIDC or OAuth 2.0. +- **Avoid** JWTs as a drop-in replacement for server-side sessions in applications that need immediate revocation (e.g., on logout), unless a token denylist or short expiry with refresh tokens is in place. +- **Avoid** storing sensitive or secret data in JWT payloads — the payload is base64-encoded, not encrypted by default. + +#### Common JWT Implementation Mistakes + +The following mistakes are frequently observed in JWT-based authentication implementations: + +- **Insecure token storage**: Storing JWTs in `localStorage` or `sessionStorage` exposes them to cross-site scripting (XSS) attacks. Prefer HttpOnly cookies with `Secure` and `SameSite=Strict` flags, or use in-memory storage (e.g., JavaScript closures). If web storage must be used, ensure a strong Content Security Policy is in place and implement token fingerprinting. +- **Missing or weak expiration**: Always set a short `exp` (expiration) claim. Long-lived tokens increase the window of exploitation if a token is compromised. Use short-lived access tokens paired with refresh tokens to balance security and usability. +- **Accepting the `none` algorithm**: Never accept tokens with `alg: none`. Always explicitly specify and enforce the expected signing algorithm server-side. Do not allow the client to influence which algorithm is used. +- **Skipping claim validation**: Always validate the `iss` (issuer), `aud` (audience), `exp` (expiration), and `nbf` (not before) claims before trusting the token. Failing to validate these claims is a common source of privilege escalation and token reuse vulnerabilities. +- **Sensitive data in the payload**: JWT payloads are only base64-encoded, not encrypted. Do not store passwords, PII, secret keys, or other sensitive information in the payload. Use JSON Web Encryption (JWE) if payload confidentiality is required. +- **Weak signing secrets**: HMAC-based JWTs (e.g., HS256) are only as secure as their secret. Use a cryptographically random secret of at least 256 bits. For distributed validation scenarios (where multiple services verify tokens), prefer asymmetric algorithms such as RS256 or ES256. +- **No token revocation mechanism**: JWTs are stateless and remain valid until they expire. Implement a token denylist, keep expiry times short, or use refresh token rotation to support logout and revocation. See the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md) for implementation guidance. + +For comprehensive JWT implementation guidance including code examples, see the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). + ## Password Managers Password managers are programs, browser plugins, or web services that automate the management of a large quantity of different credentials. Most password managers have functionality to allow users to easily use them on websites, either: diff --git a/cheatsheets/Forgot_Password_Cheat_Sheet.md b/cheatsheets/Forgot_Password_Cheat_Sheet.md index 41b734138b..0ae5fb28f2 100644 --- a/cheatsheets/Forgot_Password_Cheat_Sheet.md +++ b/cheatsheets/Forgot_Password_Cheat_Sheet.md @@ -63,7 +63,7 @@ These methods can be used together to provide a greater degree of assurance that It is essential to employ good security practices for the reset identifiers (tokens, codes, PINs, etc.). Some points don't apply to the [offline methods](#offline-methods), such as the lifetime restriction. All tokens and codes should be: - Generated using a [cryptographically secure random number generator](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). - - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [JSON Web Token Cheat Sheet](JSON_Web_Token_for_Java_Cheat_Sheet.md). + - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). - Long enough to protect against brute-force attacks. - Linked to an individual user in the database. - Invalidated after they have been used. diff --git a/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md similarity index 98% rename from cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md rename to cheatsheets/JSON_Web_Token_Cheat_Sheet.md index b1697bb036..e31e43856e 100644 --- a/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.md +++ b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md @@ -1,4 +1,4 @@ -# JSON Web Token Cheat Sheet for Java +# JSON Web Token Cheat Sheet ## Introduction @@ -51,7 +51,7 @@ HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY ) ## Objective -This cheatsheet provides tips to prevent common security issues when using JSON Web Tokens (JWT) with Java. +This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT). While the code examples use Java, the security concepts and recommendations apply to all languages and frameworks. The tips presented in this article are part of a Java project that was created to show the correct way to handle creation and validation of JSON Web Tokens. @@ -469,7 +469,7 @@ This occurs when an application stores the token in a manner exhibiting the foll 1. Store the token using the browser *sessionStorage* container, or use JavaScript *closures* with *private* variables 1. Add it as a *Bearer* HTTP `Authentication` header with JavaScript when calling services. -1. Add [fingerprint](JSON_Web_Token_for_Java_Cheat_Sheet.md#token-sidejacking) information to the token. +1. Add [fingerprint](JSON_Web_Token_Cheat_Sheet.md#token-sidejacking) information to the token. By storing the token in browser *sessionStorage* container it exposes the token to being stolen through an XSS attack. However, fingerprints added to the token prevent reuse of the stolen token by the attacker on their machine. To close a maximum of exploitation surfaces for an attacker, add a browser [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) to harden the execution context. diff --git a/cheatsheets/REST_Security_Cheat_Sheet.md b/cheatsheets/REST_Security_Cheat_Sheet.md index 3247802486..7d0282d088 100644 --- a/cheatsheets/REST_Security_Cheat_Sheet.md +++ b/cheatsheets/REST_Security_Cheat_Sheet.md @@ -56,7 +56,7 @@ Some claims have been standardized and should be present in JWT used for access - `exp` or expiration time - is the current time before the end of the validity period of this token? - `nbf` or not before time - is the current time after the start of the validity period of this token? -As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [JSON_Web_Token_for_Java_Cheat_Sheet](JSON_Web_Token_for_Java_Cheat_Sheet.md#token-explicit-revocation-by-the-user) for further details. +As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md#token-explicit-revocation-by-the-user) for further details. ## API Keys From 2144c9e0ce47876e5f82259d4df1f8b80c41a03a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:25:16 +0000 Subject: [PATCH 3/5] Consolidate JWT content into Authentication Cheat Sheet; remove standalone JWT Cheat Sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Expand Authentication_Cheat_Sheet.md JWT section to cover all topics from the removed JWT CS: token structure overview, session management reference, token sidejacking (fingerprinting), expanded revocation options (denylist + fingerprinting+sessionStorage), JWE reference (RFC 7516), and Further Reading (RFC 8725, JWT Attack Playbook) - Delete JSON_Web_Token_Cheat_Sheet.md completely (no content duplication) - Update Forgot_Password_Cheat_Sheet.md link → Auth CS JWT section - Update REST_Security_Cheat_Sheet.md link → Auth CS JWT section - Update IndexASVS, IndexASVS4, IndexMASVS, IndexProactiveControls, IndexTopTen to replace JWT CS link with Auth CS JWT section link - Regenerate Index.md via script (count: 112 → 111, JWT CS entry removed) Closes #1973 Closes #1176 Agent-Logs-Url: https://github.com/OWASP/CheatSheetSeries/sessions/c2eb6d41-32a8-46f5-bdbf-e0abc98044ad Co-authored-by: mackowski <35339942+mackowski@users.noreply.github.com> --- Index.md | 4 +- IndexASVS.md | 2 +- IndexASVS4.md | 2 +- IndexMASVS.md | 2 +- IndexProactiveControls.md | 2 +- IndexTopTen.md | 2 +- cheatsheets/Authentication_Cheat_Sheet.md | 26 +- cheatsheets/Forgot_Password_Cheat_Sheet.md | 2 +- cheatsheets/JSON_Web_Token_Cheat_Sheet.md | 629 --------------------- cheatsheets/REST_Security_Cheat_Sheet.md | 2 +- 10 files changed, 24 insertions(+), 649 deletions(-) delete mode 100644 cheatsheets/JSON_Web_Token_Cheat_Sheet.md diff --git a/Index.md b/Index.md index 0f6ae55e82..f3c190ffdc 100644 --- a/Index.md +++ b/Index.md @@ -1,6 +1,6 @@ # Index Alphabetical -**112** cheat sheets available. +**111** cheat sheets available. *Icons beside the cheat sheet name indicate in which language(s) code snippet(s) are provided.* @@ -120,8 +120,6 @@ [Java Security Cheat Sheet](cheatsheets/Java_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) -[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Json](assets/Index_Json.svg) ![Sql](assets/Index_Sql.svg) - ## K [Key Management Cheat Sheet](cheatsheets/Key_Management_Cheat_Sheet.md) diff --git a/IndexASVS.md b/IndexASVS.md index ad406ccf35..11ea8e1b18 100644 --- a/IndexASVS.md +++ b/IndexASVS.md @@ -425,7 +425,7 @@ None. ### V9.1 Token source and integrity -[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) +[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/IndexASVS4.md b/IndexASVS4.md index e52e9bde31..1184a3d12e 100644 --- a/IndexASVS4.md +++ b/IndexASVS4.md @@ -241,7 +241,7 @@ None. ### V3.5 Token-based Session Management -[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) +[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) [REST Security Cheat Sheet](cheatsheets/REST_Security_Cheat_Sheet.md) diff --git a/IndexMASVS.md b/IndexMASVS.md index 3868232221..cd8f47407c 100644 --- a/IndexMASVS.md +++ b/IndexMASVS.md @@ -46,7 +46,7 @@ This index is based on version [2.1.0](https://github.com/OWASP/owasp-masvs/rele [Access Control Cheat Sheet](cheatsheets/Access_Control_Cheat_Sheet.md) -[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) +[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) diff --git a/IndexProactiveControls.md b/IndexProactiveControls.md index 31eca98c08..c957684823 100644 --- a/IndexProactiveControls.md +++ b/IndexProactiveControls.md @@ -104,7 +104,7 @@ This cheat sheet will help users of the [OWASP Top Ten Proactive Controls 2018]( [JAAS Cheat Sheet](cheatsheets/JAAS_Cheat_Sheet.md) -[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) +[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) diff --git a/IndexTopTen.md b/IndexTopTen.md index fb9058f52f..5d2b913ab9 100644 --- a/IndexTopTen.md +++ b/IndexTopTen.md @@ -60,7 +60,7 @@ This cheat sheet will help users of the [OWASP Top Ten](https://owasp.org/Top10/ - [Choosing and Using Security Questions Cheat Sheet](cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.md) - [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) - [Denial of Service Cheat Sheet](cheatsheets/Denial_of_Service_Cheat_Sheet.md) -- [JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) +- [Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) - [Multifactor Authentication Cheat Sheet](cheatsheets/Multifactor_Authentication_Cheat_Sheet.md) - [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) - [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/cheatsheets/Authentication_Cheat_Sheet.md b/cheatsheets/Authentication_Cheat_Sheet.md index e6de83debb..30526bdc9f 100644 --- a/cheatsheets/Authentication_Cheat_Sheet.md +++ b/cheatsheets/Authentication_Cheat_Sheet.md @@ -345,7 +345,7 @@ U2F augments password-based authentication using a hardware token (typically USB ### JSON Web Tokens (JWT) -JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are a compact, URL-safe way to represent claims between two parties. They are widely used as bearer tokens in authentication and authorization flows, including as OIDC ID tokens and OAuth 2.0 access tokens. +JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are a compact, URL-safe way to represent claims between two parties. A JWT consists of three base64url-encoded parts separated by dots: a **header** (algorithm and token type), a **payload** (claims such as subject, issuer, expiration), and a **signature**. They are widely used as bearer tokens in authentication and authorization flows, including as OIDC ID tokens and OAuth 2.0 access tokens. #### When to Use (and Not Use) JWTs @@ -353,21 +353,27 @@ JWTs are well-suited for stateless authentication across distributed systems and - **Use** JWTs when stateless, cross-service token passing is required or when following standards like OIDC or OAuth 2.0. - **Avoid** JWTs as a drop-in replacement for server-side sessions in applications that need immediate revocation (e.g., on logout), unless a token denylist or short expiry with refresh tokens is in place. -- **Avoid** storing sensitive or secret data in JWT payloads — the payload is base64-encoded, not encrypted by default. +- **Avoid** storing sensitive or secret data in JWT payloads — the payload is base64url-encoded, not encrypted by default. + +If your application does not need to be fully stateless, consider using traditional server-side sessions and follow the [Session Management Cheat Sheet](Session_Management_Cheat_Sheet.md). #### Common JWT Implementation Mistakes The following mistakes are frequently observed in JWT-based authentication implementations: -- **Insecure token storage**: Storing JWTs in `localStorage` or `sessionStorage` exposes them to cross-site scripting (XSS) attacks. Prefer HttpOnly cookies with `Secure` and `SameSite=Strict` flags, or use in-memory storage (e.g., JavaScript closures). If web storage must be used, ensure a strong Content Security Policy is in place and implement token fingerprinting. -- **Missing or weak expiration**: Always set a short `exp` (expiration) claim. Long-lived tokens increase the window of exploitation if a token is compromised. Use short-lived access tokens paired with refresh tokens to balance security and usability. -- **Accepting the `none` algorithm**: Never accept tokens with `alg: none`. Always explicitly specify and enforce the expected signing algorithm server-side. Do not allow the client to influence which algorithm is used. -- **Skipping claim validation**: Always validate the `iss` (issuer), `aud` (audience), `exp` (expiration), and `nbf` (not before) claims before trusting the token. Failing to validate these claims is a common source of privilege escalation and token reuse vulnerabilities. -- **Sensitive data in the payload**: JWT payloads are only base64-encoded, not encrypted. Do not store passwords, PII, secret keys, or other sensitive information in the payload. Use JSON Web Encryption (JWE) if payload confidentiality is required. -- **Weak signing secrets**: HMAC-based JWTs (e.g., HS256) are only as secure as their secret. Use a cryptographically random secret of at least 256 bits. For distributed validation scenarios (where multiple services verify tokens), prefer asymmetric algorithms such as RS256 or ES256. -- **No token revocation mechanism**: JWTs are stateless and remain valid until they expire. Implement a token denylist, keep expiry times short, or use refresh token rotation to support logout and revocation. See the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md) for implementation guidance. +- **Accepting the `none` algorithm**: Never accept tokens signed with `alg: none`. Always explicitly specify and enforce the expected signing algorithm server-side; do not allow the client to dictate which algorithm is used. +- **Skipping claim validation**: Always validate the `iss` (issuer), `aud` (audience), `exp` (expiration), and `nbf` (not before) claims. Failing to validate these is a common source of privilege escalation and token reuse vulnerabilities. +- **Missing or weak expiration**: Always set a short `exp` claim. Long-lived tokens extend the window of exploitation if compromised. Use short-lived access tokens paired with refresh tokens to balance security and usability. +- **Weak signing secrets**: HMAC-based JWTs (e.g., HS256) are only as secure as their signing secret. Use a cryptographically random secret of at least 256 bits. For distributed validation scenarios where multiple services verify tokens, prefer asymmetric algorithms such as RS256 or ES256 to avoid sharing a symmetric key across services. +- **Sensitive data in the payload**: JWT payloads are base64url-encoded, not encrypted. Do not store passwords, PII, secret keys, or other sensitive data in the payload. Use JSON Web Encryption (JWE, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516)) if payload confidentiality is required. +- **Insecure token storage**: Storing JWTs in `localStorage` or `sessionStorage` exposes them to cross-site scripting (XSS) attacks. Prefer HttpOnly cookies with `Secure` and `SameSite=Strict` flags, or use in-memory storage (e.g., JavaScript closures). If web storage must be used, enforce a strict Content Security Policy and apply token fingerprinting (see below). +- **Token sidejacking**: A stolen or intercepted token can be replayed by an attacker. Mitigate by binding the token to the user's browser session: generate a random fingerprint string at authentication time, store it in a hardened HttpOnly, Secure, SameSite=Strict cookie, and embed only its SHA-256 hash in the JWT payload. Validate the fingerprint on every request; any token replayed without the matching cookie must be rejected. +- **No token revocation**: JWTs remain valid until they expire; there is no built-in logout. Options include: (a) short expiry times with refresh token rotation; (b) a server-side token denylist that stores a SHA-256 digest of revoked tokens until their natural expiry; or (c) combining token fingerprinting with `sessionStorage` so that clearing storage effectively prevents further token use. + +#### Further Reading -For comprehensive JWT implementation guidance including code examples, see the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). +- [RFC 8725 — JSON Web Token Best Current Practices](https://datatracker.ietf.org/doc/html/rfc8725) +- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) — documents known JWT attacks and misconfigurations ## Password Managers diff --git a/cheatsheets/Forgot_Password_Cheat_Sheet.md b/cheatsheets/Forgot_Password_Cheat_Sheet.md index 0ae5fb28f2..a6d1d5f48b 100644 --- a/cheatsheets/Forgot_Password_Cheat_Sheet.md +++ b/cheatsheets/Forgot_Password_Cheat_Sheet.md @@ -63,7 +63,7 @@ These methods can be used together to provide a greater degree of assurance that It is essential to employ good security practices for the reset identifiers (tokens, codes, PINs, etc.). Some points don't apply to the [offline methods](#offline-methods), such as the lifetime restriction. All tokens and codes should be: - Generated using a [cryptographically secure random number generator](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). - - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). + - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [Authentication Cheat Sheet](Authentication_Cheat_Sheet.md#json-web-tokens-jwt). - Long enough to protect against brute-force attacks. - Linked to an individual user in the database. - Invalidated after they have been used. diff --git a/cheatsheets/JSON_Web_Token_Cheat_Sheet.md b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md deleted file mode 100644 index e31e43856e..0000000000 --- a/cheatsheets/JSON_Web_Token_Cheat_Sheet.md +++ /dev/null @@ -1,629 +0,0 @@ -# JSON Web Token Cheat Sheet - -## Introduction - -Many applications use **JSON Web Tokens** (JWT) to allow the client to indicate its identity for further exchange after authentication. - -From [JWT.IO](https://jwt.io/introduction): - -> JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA. - -JWTs are used to carry information related to the identity and characteristics (claims) of a client. This information is signed by the server to ensure it has not been tampered with after being sent to the client. This prevents an attacker from modifying the identity or characteristics — for example, changing the role from a simple user to an admin or altering the client's login. - -The token is created during authentication (it is issued upon successful authentication) and is verified by the server before any processing. Applications use the token to allow a client to present what is essentially an "identity card" to the server. The server can then securely verify the token's validity and integrity. This approach is stateless and portable, meaning it works across different client and server technologies, and over various transport channels — although HTTP is the most commonly used. - -## Token Structure - -Token structure example taken from [JWT.IO](https://jwt.io/#debugger): - -`[Base64(HEADER)].[Base64(PAYLOAD)].[Base64(SIGNATURE)]` - -```text -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. -eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. -TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ -``` - -Chunk 1: **Header** - -```json -{ - "alg": "HS256", - "typ": "JWT" -} -``` - -Chunk 2: **Payload** - -```json -{ - "sub": "1234567890", - "name": "John Doe", - "admin": true -} -``` - -Chunk 3: **Signature** - -```javascript -HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY ) -``` - -## Objective - -This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT). While the code examples use Java, the security concepts and recommendations apply to all languages and frameworks. - -The tips presented in this article are part of a Java project that was created to show the correct way to handle creation and validation of JSON Web Tokens. - -You can find the Java project [here](https://github.com/righettod/poc-jwt), it uses the official [JWT library](https://jwt.io/#libraries). - -In the rest of the article, the term **token** refers to the **JSON Web Tokens** (JWT). - -## Consideration about Using JWT - -Even if a JWT is "easy" to use and allow to expose services (mostly REST style) in a stateless way, it's not the solution that fits for all applications because it comes with some caveats, like for example the question of the storage of the token (tackled in this cheatsheet) and others... - -If your application does not need to be fully stateless, you can consider using traditional session system provided by all web frameworks and follow the advice from the dedicated [session management cheat sheet](Session_Management_Cheat_Sheet.md). However, for stateless applications, when well implemented, it's a good candidate. - -## Issues - -### None Hashing Algorithm - -#### Symptom - -This attack, described [here](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/), occurs when an attacker alters the token and changes the hashing algorithm to indicate, through the *none* keyword, that the integrity of the token has already been verified. As explained in the link above *some libraries treated tokens signed with the none algorithm as a valid token with a verified signature*, so an attacker can alter the token claims and the modified token will still be trusted by the application. - -#### How to Prevent - -First, use a JWT library that is not exposed to this vulnerability. - -Last, during token validation, explicitly request that the expected algorithm was used. - -#### Implementation Example - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; - -... - -//Create a verification context for the token requesting -//explicitly the use of the HMAC-256 hashing algorithm -JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)).build(); - -//Verify the token, if the verification fail then a exception is thrown -DecodedJWT decodedToken = verifier.verify(token); -``` - -### Token Sidejacking - -#### Symptom - -This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity. - -#### How to Prevent - -One way to prevent this is by adding a "user context" to the token. The user context should consist of the following: - -- A random string generated during the authentication phase. This string is sent to the client as a hardened cookie (with the following flags: [HttpOnly + Secure](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies), [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies), [Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie), and [cookie prefixes](https://googlechrome.github.io/samples/cookie-prefixes/)). Avoid setting the *expires* header so the cookie is cleared when the browser is closed. Set *Max-Age* to a value equal to or less than the JWT's expiry time — never more. -- A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing the attacker to read the random string value and setting the expected cookie. - -Avoid using IP addresses as part of the context. IP addresses can change during a single session due to legitimate reasons — for example, when a user accesses the application on a mobile device and switches network providers. Additionally, IP tracking can raise concerns related to [GDPR compliance](https://gdpr.eu/) in the EU. - -During token validation, if the received token does not contain the correct context (e.g., if it is being replayed by an attacker), it must be rejected. - -#### Implementation example - -Code to create the token after successful authentication. - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; -// Random data generator -private SecureRandom secureRandom = new SecureRandom(); - -... - -//Generate a random string that will constitute the fingerprint for this user -byte[] randomFgp = new byte[50]; -secureRandom.nextBytes(randomFgp); -String userFingerprint = DatatypeConverter.printHexBinary(randomFgp); - -//Add the fingerprint in a hardened cookie - Add cookie manually because -//SameSite attribute is not supported by javax.servlet.http.Cookie class -String fingerprintCookie = "__Secure-Fgp=" + userFingerprint - + "; SameSite=Strict; HttpOnly; Secure"; -response.addHeader("Set-Cookie", fingerprintCookie); - -//Compute a SHA256 hash of the fingerprint in order to store the -//fingerprint hash (instead of the raw value) in the token -//to prevent an XSS to be able to read the fingerprint and -//set the expected cookie itself -MessageDigest digest = MessageDigest.getInstance("SHA-256"); -byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); -String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); - -//Create the token with a validity of 15 minutes and client context (fingerprint) information -Calendar c = Calendar.getInstance(); -Date now = c.getTime(); -c.add(Calendar.MINUTE, 15); -Date expirationDate = c.getTime(); -Map headerClaims = new HashMap<>(); -headerClaims.put("typ", "JWT"); -String token = JWT.create().withSubject(login) - .withExpiresAt(expirationDate) - .withIssuer(this.issuerID) - .withIssuedAt(now) - .withNotBefore(now) - .withClaim("userFingerprint", userFingerprintHash) - .withHeader(headerClaims) - .sign(Algorithm.HMAC256(this.keyHMAC)); -``` - -Code to validate the token. - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; - -... - -//Retrieve the user fingerprint from the dedicated cookie -String userFingerprint = null; -if (request.getCookies() != null && request.getCookies().length > 0) { - List cookies = Arrays.stream(request.getCookies()).collect(Collectors.toList()); - Optional cookie = cookies.stream().filter(c -> "__Secure-Fgp" - .equals(c.getName())).findFirst(); - if (cookie.isPresent()) { - userFingerprint = cookie.get().getValue(); - } -} - -//Compute a SHA256 hash of the received fingerprint in cookie in order to compare -//it to the fingerprint hash stored in the token -MessageDigest digest = MessageDigest.getInstance("SHA-256"); -byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); -String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); - -//Create a verification context for the token -JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)) - .withIssuer(issuerID) - .withClaim("userFingerprint", userFingerprintHash) - .build(); - -//Verify the token, if the verification fail then an exception is thrown -DecodedJWT decodedToken = verifier.verify(token); -``` - -### No Built-In Token Revocation by the User - -#### Symptom - -This problem is inherent to JWT because a token only becomes invalid when it expires. The user has no built-in feature to explicitly revoke the validity of a token. This means that if it is stolen, a user cannot revoke the token itself thereby blocking the attacker. - -#### How to Prevent - -Since JWTs are stateless, There is no session maintained on the server(s) serving client requests. As such, there is no session to invalidate on the server side. A well implemented Token Sidejacking solution (as explained above) should alleviate the need for maintaining denylist on server side. This is because a hardened cookie used in the Token Sidejacking can be considered as secure as a session ID used in the traditional session system, and unless both the cookie and the JWT are intercepted/stolen, the JWT is unusable. A logout can thus be 'simulated' by clearing the JWT from session storage. If the user chooses to close the browser instead, then both the cookie and sessionStorage are cleared automatically. - -Another way to protect against this is to implement a token denylist that will be used to mimic the "logout" feature that exists with traditional session management system. - -The denylist will keep a digest (SHA-256 encoded in HEX) of the token with a revocation date. This entry must endure at least until the expiration of the token. - -When the user wants to "logout" then it call a dedicated service that will add the provided user token to the denylist resulting in an immediate invalidation of the token for further usage in the application. - -#### Implementation Example - -##### Block List Storage - -A database table with the following structure will be used as the central denylist storage. - -``` sql -create table if not exists revoked_token(jwt_token_digest varchar(255) primary key, -revocation_date timestamp default now()); -``` - -##### Token Revocation Management - -Code in charge of adding a token to the denylist and checking if a token is revoked. - -``` java -/** -* Handle the revocation of the token (logout). -* Use a DB in order to allow multiple instances to check for revoked token -* and allow cleanup at centralized DB level. -*/ -public class TokenRevoker { - - /** DB Connection */ - @Resource("jdbc/storeDS") - private DataSource storeDS; - - /** - * Verify if a digest encoded in HEX of the ciphered token is present - * in the revocation table - * - * @param jwtInHex Token encoded in HEX - * @return Presence flag - * @throws Exception If any issue occur during communication with DB - */ - public boolean isTokenRevoked(String jwtInHex) throws Exception { - boolean tokenIsPresent = false; - if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Compute a SHA256 of the ciphered token - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] cipheredTokenDigest = digest.digest(cipheredToken); - String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); - - //Search token digest in HEX in DB - try (Connection con = this.storeDS.getConnection()) { - String query = "select jwt_token_digest from revoked_token where jwt_token_digest = ?"; - try (PreparedStatement pStatement = con.prepareStatement(query)) { - pStatement.setString(1, jwtTokenDigestInHex); - try (ResultSet rSet = pStatement.executeQuery()) { - tokenIsPresent = rSet.next(); - } - } - } - } - - return tokenIsPresent; - } - - - /** - * Add a digest encoded in HEX of the ciphered token to the revocation token table - * - * @param jwtInHex Token encoded in HEX - * @throws Exception If any issue occur during communication with DB - */ - public void revokeToken(String jwtInHex) throws Exception { - if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Compute a SHA256 of the ciphered token - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] cipheredTokenDigest = digest.digest(cipheredToken); - String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); - - //Check if the token digest in HEX is already in the DB and add it if it is absent - if (!this.isTokenRevoked(jwtInHex)) { - try (Connection con = this.storeDS.getConnection()) { - String query = "insert into revoked_token(jwt_token_digest) values(?)"; - int insertedRecordCount; - try (PreparedStatement pStatement = con.prepareStatement(query)) { - pStatement.setString(1, jwtTokenDigestInHex); - insertedRecordCount = pStatement.executeUpdate(); - } - if (insertedRecordCount != 1) { - throw new IllegalStateException("Number of inserted record is invalid," + - " 1 expected but is " + insertedRecordCount); - } - } - } - - } - } -``` - -### Token Information Disclosure - -#### Symptom - -This attack occurs when an attacker has access to a token (or a set of tokens) and extracts information stored in it (the contents of JWTs are base64 encoded, but is not encrypted by default) in order to obtain information about the system. Information can be for example the security roles, login format... - -#### How to Prevent - -A way to protect against this attack is to cipher the token using, for example, a symmetric algorithm. - -It's also important to protect the ciphered data against attack like [Padding Oracle](https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/02-Testing_for_Padding_Oracle.html) or any other attack using cryptanalysis. - -In order to achieve all these goals, the *AES-[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode)* algorithm is used which provides *Authenticated Encryption with Associated Data*. - -More details from [here](https://github.com/google/tink/blob/master/docs/PRIMITIVES.md#deterministic-authenticated-encryption-with-associated-data): - -```text -AEAD primitive (Authenticated Encryption with Associated Data) provides functionality of symmetric -authenticated encryption. - -Implementations of this primitive are secure against adaptive chosen ciphertext attacks. - -When encrypting a plaintext one can optionally provide associated data that should be authenticated -but not encrypted. - -That is, the encryption with associated data ensures authenticity (ie. who the sender is) and -integrity (ie. data has not been tampered with) of that data, but not its secrecy. - -See RFC5116: https://tools.ietf.org/html/rfc5116 -``` - -**Note:** - -Here ciphering is added mainly to hide internal information but it's very important to remember that the first protection against tampering of the JWT is the signature. So, the token signature and its verification must be always in place. - -#### Implementation Example - -##### Token Ciphering - -Code in charge of managing the ciphering. [Google Tink](https://github.com/google/tink) dedicated crypto library is used to handle ciphering operations in order to use built-in best practices provided by this library. - -``` java -/** - * Handle ciphering and deciphering of the token using AES-GCM. - * - * @see "https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md" - */ -public class TokenCipher { - - /** - * Constructor - Register AEAD configuration - * - * @throws Exception If any issue occur during AEAD configuration registration - */ - public TokenCipher() throws Exception { - AeadConfig.register(); - } - - /** - * Cipher a JWT - * - * @param jwt Token to cipher - * @param keysetHandle Pointer to the keyset handle - * @return The ciphered version of the token encoded in HEX - * @throws Exception If any issue occur during token ciphering operation - */ - public String cipherToken(String jwt, KeysetHandle keysetHandle) throws Exception { - //Verify parameters - if (jwt == null || jwt.isEmpty() || keysetHandle == null) { - throw new IllegalArgumentException("Both parameters must be specified!"); - } - - //Get the primitive - Aead aead = AeadFactory.getPrimitive(keysetHandle); - - //Cipher the token - byte[] cipheredToken = aead.encrypt(jwt.getBytes(), null); - - return DatatypeConverter.printHexBinary(cipheredToken); - } - - /** - * Decipher a JWT - * - * @param jwtInHex Token to decipher encoded in HEX - * @param keysetHandle Pointer to the keyset handle - * @return The token in clear text - * @throws Exception If any issue occur during token deciphering operation - */ - public String decipherToken(String jwtInHex, KeysetHandle keysetHandle) throws Exception { - //Verify parameters - if (jwtInHex == null || jwtInHex.isEmpty() || keysetHandle == null) { - throw new IllegalArgumentException("Both parameters must be specified !"); - } - - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Get the primitive - Aead aead = AeadFactory.getPrimitive(keysetHandle); - - //Decipher the token - byte[] decipheredToken = aead.decrypt(cipheredToken, null); - - return new String(decipheredToken); - } -} -``` - -##### Creation / Validation of the Token - -Use the token ciphering handler during the creation and the validation of the token. - -Load keys (ciphering key was generated and stored using [Google Tink](https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md#generating-new-keysets)) and setup cipher. - -``` java -//Load keys from configuration text/json files in order to avoid to storing keys as a String in JVM memory -private transient byte[] keyHMAC = Files.readAllBytes(Paths.get("src", "main", "conf", "key-hmac.txt")); -private transient KeysetHandle keyCiphering = CleartextKeysetHandle.read(JsonKeysetReader.withFile( -Paths.get("src", "main", "conf", "key-ciphering.json").toFile())); - -... - -//Init token ciphering handler -TokenCipher tokenCipher = new TokenCipher(); -``` - -Token creation. - -``` java -//Generate the JWT token using the JWT API... -//Cipher the token (String JSON representation) -String cipheredToken = tokenCipher.cipherToken(token, this.keyCiphering); -//Send the ciphered token encoded in HEX to the client in HTTP response... -``` - -Token validation. - -``` java -//Retrieve the ciphered token encoded in HEX from the HTTP request... -//Decipher the token -String token = tokenCipher.decipherToken(cipheredToken, this.keyCiphering); -//Verify the token using the JWT API... -//Verify access... -``` - -### Token Storage on Client Side - -#### Symptom - -This occurs when an application stores the token in a manner exhibiting the following behavior: - -- Automatically sent by the browser (*Cookie* storage). -- Retrieved even if the browser is restarted (Use of browser *localStorage* container). -- Retrieved in case of [XSS](Cross_Site_Scripting_Prevention_Cheat_Sheet.md) issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage). - -#### How to Prevent - -1. Store the token using the browser *sessionStorage* container, or use JavaScript *closures* with *private* variables -1. Add it as a *Bearer* HTTP `Authentication` header with JavaScript when calling services. -1. Add [fingerprint](JSON_Web_Token_Cheat_Sheet.md#token-sidejacking) information to the token. - -By storing the token in browser *sessionStorage* container it exposes the token to being stolen through an XSS attack. However, fingerprints added to the token prevent reuse of the stolen token by the attacker on their machine. To close a maximum of exploitation surfaces for an attacker, add a browser [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) to harden the execution context. - -But, we know that *sessionStorage* is not always practical due to its per-tab scope, and the storage method for tokens should balance *security* and *usability*. - -*LocalStorage* is a better method than *sessionStorage* for usability because it allows the session to persist between browser restarts and across tabs, but you must use strict security controls: - -- Tokens stored in *localStorage* should have *short expiration times* (e.g., *15-30 minutes idle timeout, 8-hour absolute timeout*). -- Implement mechanisms such as *token rotation* and *refresh tokens* to minimize risk. - -If *session persistence across tabs* and *sessionStorage* are required, consider using *BroadcastChannel API* or *Single Sign-On (SSO)* to re-authenticate users automatically when they open new tabs. - -An alternative to storing token in browser *sessionStorage* or in *localStorage* is to use JavaScript private variable or Closures. In this, access to all web requests are routed through a JavaScript module that encapsulates the token in a private variable which can not be accessed other than from within the module. - -*Note:* - -- The remaining case is when an attacker uses the user's browsing context as a proxy to use the target application through the legitimate user but the Content Security Policy can prevent communication with non expected domains. -- It's also possible to implement the authentication service in a way that the token is issued within a hardened cookie, but in this case, protection against a [Cross-Site Request Forgery](Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) attack must be implemented. - -#### Implementation Example - -JavaScript code to store the token after authentication. - -``` javascript -/* Handle request for JWT token and local storage*/ -function authenticate() { - const login = $("#login").val(); - const postData = "login=" + encodeURIComponent(login) + "&password=test"; - - $.post("/services/authenticate", postData, function (data) { - if (data.status == "Authentication successful!") { - ... - sessionStorage.setItem("token", data.token); - } - else { - ... - sessionStorage.removeItem("token"); - } - }) - .fail(function (jqXHR, textStatus, error) { - ... - sessionStorage.removeItem("token"); - }); -} -``` - -JavaScript code to add the token as a *Bearer* HTTP Authentication header when calling a service, for example a service to validate token here. - -``` javascript -/* Handle request for JWT token validation */ -function validateToken() { - var token = sessionStorage.getItem("token"); - - if (token == undefined || token == "") { - $("#infoZone").removeClass(); - $("#infoZone").addClass("alert alert-warning"); - $("#infoZone").text("Obtain a JWT token first :)"); - return; - } - - $.ajax({ - url: "/services/validate", - type: "POST", - beforeSend: function (xhr) { - xhr.setRequestHeader("Authorization", "bearer " + token); - }, - success: function (data) { - ... - }, - error: function (jqXHR, textStatus, error) { - ... - }, - }); -} -``` - -JavaScript code to implement closures with private variables: - -``` javascript -function myFetchModule() { - // Protect the original 'fetch' from getting overwritten via XSS - const fetch = window.fetch; - - const authOrigins = ["https://yourorigin", "http://localhost"]; - let token = ''; - - this.setToken = (value) => { - token = value - } - - this.fetch = (resource, options) => { - let req = new Request(resource, options); - destOrigin = new URL(req.url).origin; - if (token && authOrigins.includes(destOrigin)) { - req.headers.set('Authorization', token); - } - return fetch(req) - } -} - -... - -// usage: -const myFetch = new myFetchModule() - -function login() { - fetch("/api/login") - .then((res) => { - if (res.status == 200) { - return res.json() - } else { - throw Error(res.statusText) - } - }) - .then(data => { - myFetch.setToken(data.token) - console.log("Token received and stored.") - }) - .catch(console.error) -} - -... - -// after login, subsequent api calls: -function makeRequest() { - myFetch.fetch("/api/hello", {headers: {"MyHeader": "foobar"}}) - .then((res) => { - if (res.status == 200) { - return res.text() - } else { - throw Error(res.statusText) - } - }).then(responseText => console.log("helloResponse", responseText)) - .catch(console.error) -} -``` - -### Weak Token Secret - -#### Symptom - -When the token is protected using an HMAC based algorithm, the security of the token is entirely dependent on the strength of the secret used with the HMAC. If an attacker can obtain a valid JWT, they can then carry out an offline attack and attempt to crack the secret using tools such as [John the Ripper](https://github.com/magnumripper/JohnTheRipper) or [Hashcat](https://github.com/hashcat/hashcat). - -If they are successful, they would then be able to modify the token and re-sign it with the key they had obtained. This could let them escalate their privileges, compromise other users' accounts, or perform other actions depending on the contents of the JWT. - -There are a number of [guides](https://www.notsosecure.com/crafting-way-json-web-tokens/) that document this process in greater detail. - -#### How to Prevent - -The simplest way to prevent this attack is to ensure that the secret used to sign the JWTs is strong and unique, in order to make it harder for an attacker to crack. As this secret would never need to be typed by a human, it should be at least 64 characters, and generated using a [secure source of randomness](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). - -Alternatively, consider the use of tokens that are signed with RSA rather than using an HMAC and secret key. - -#### Further Reading - -- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) - A project documents the known attacks and potential security vulnerabilities and misconfigurations of JSON Web Tokens. -- [JWT Best Practices Internet Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-jwt-bcp/) diff --git a/cheatsheets/REST_Security_Cheat_Sheet.md b/cheatsheets/REST_Security_Cheat_Sheet.md index 7d0282d088..8df3f953ad 100644 --- a/cheatsheets/REST_Security_Cheat_Sheet.md +++ b/cheatsheets/REST_Security_Cheat_Sheet.md @@ -56,7 +56,7 @@ Some claims have been standardized and should be present in JWT used for access - `exp` or expiration time - is the current time before the end of the validity period of this token? - `nbf` or not before time - is the current time after the start of the validity period of this token? -As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md#token-explicit-revocation-by-the-user) for further details. +As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [Authentication Cheat Sheet](Authentication_Cheat_Sheet.md#json-web-tokens-jwt) for further details. ## API Keys From 34c74c4b11e4167997e600d5d18d8f620e0fdee7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:00:06 +0000 Subject: [PATCH 4/5] Restore dedicated JWT Cheat Sheet; trim Authentication Cheat Sheet JWT section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per reviewer feedback ("move all the JWT content into a JWT cheatsheet and link the AuthN cheatsheet to it; keep these to small cheatsheets"): - Restore cheatsheets/JSON_Web_Token_Cheat_Sheet.md as standalone comprehensive cheatsheet (token structure, sidejacking, revocation, info disclosure, storage, weak secrets, implementation examples) - Fix self-reference anchor in JWT CS (JSON_Web_Token_Cheat_Sheet.md #token-sidejacking → #token-sidejacking) - Trim Authentication_Cheat_Sheet.md JWT section to a 2-sentence paragraph with links to JWT CS and Session Management CS - Update Forgot_Password_Cheat_Sheet.md → JSON_Web_Token_Cheat_Sheet.md - Update REST_Security_Cheat_Sheet.md → JWT CS #no-built-in-token-revocation - Restore IndexASVS, IndexASVS4, IndexMASVS, IndexProactiveControls, IndexTopTen to link to JWT CS (not Auth CS anchor) - Regenerate Index.md (count: 111 → 112) Agent-Logs-Url: https://github.com/OWASP/CheatSheetSeries/sessions/1caf9112-f7dd-4b0a-b47c-c0da156cbadd Co-authored-by: mackowski <35339942+mackowski@users.noreply.github.com> --- Index.md | 4 +- IndexASVS.md | 2 +- IndexASVS4.md | 2 +- IndexMASVS.md | 2 +- IndexProactiveControls.md | 2 +- IndexTopTen.md | 2 +- cheatsheets/Authentication_Cheat_Sheet.md | 30 +- cheatsheets/Forgot_Password_Cheat_Sheet.md | 2 +- cheatsheets/JSON_Web_Token_Cheat_Sheet.md | 629 +++++++++++++++++++++ cheatsheets/REST_Security_Cheat_Sheet.md | 2 +- 10 files changed, 641 insertions(+), 36 deletions(-) create mode 100644 cheatsheets/JSON_Web_Token_Cheat_Sheet.md diff --git a/Index.md b/Index.md index f3c190ffdc..0f6ae55e82 100644 --- a/Index.md +++ b/Index.md @@ -1,6 +1,6 @@ # Index Alphabetical -**111** cheat sheets available. +**112** cheat sheets available. *Icons beside the cheat sheet name indicate in which language(s) code snippet(s) are provided.* @@ -120,6 +120,8 @@ [Java Security Cheat Sheet](cheatsheets/Java_Security_Cheat_Sheet.md) ![Java](assets/Index_Java.svg) ![Xml](assets/Index_Xml.svg) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) ![Javascript](assets/Index_Javascript.svg) ![Java](assets/Index_Java.svg) ![Json](assets/Index_Json.svg) ![Sql](assets/Index_Sql.svg) + ## K [Key Management Cheat Sheet](cheatsheets/Key_Management_Cheat_Sheet.md) diff --git a/IndexASVS.md b/IndexASVS.md index 11ea8e1b18..ad406ccf35 100644 --- a/IndexASVS.md +++ b/IndexASVS.md @@ -425,7 +425,7 @@ None. ### V9.1 Token source and integrity -[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/IndexASVS4.md b/IndexASVS4.md index 1184a3d12e..e52e9bde31 100644 --- a/IndexASVS4.md +++ b/IndexASVS4.md @@ -241,7 +241,7 @@ None. ### V3.5 Token-based Session Management -[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [REST Security Cheat Sheet](cheatsheets/REST_Security_Cheat_Sheet.md) diff --git a/IndexMASVS.md b/IndexMASVS.md index cd8f47407c..3868232221 100644 --- a/IndexMASVS.md +++ b/IndexMASVS.md @@ -46,7 +46,7 @@ This index is based on version [2.1.0](https://github.com/OWASP/owasp-masvs/rele [Access Control Cheat Sheet](cheatsheets/Access_Control_Cheat_Sheet.md) -[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) diff --git a/IndexProactiveControls.md b/IndexProactiveControls.md index c957684823..31eca98c08 100644 --- a/IndexProactiveControls.md +++ b/IndexProactiveControls.md @@ -104,7 +104,7 @@ This cheat sheet will help users of the [OWASP Top Ten Proactive Controls 2018]( [JAAS Cheat Sheet](cheatsheets/JAAS_Cheat_Sheet.md) -[Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) +[JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) diff --git a/IndexTopTen.md b/IndexTopTen.md index 5d2b913ab9..fb9058f52f 100644 --- a/IndexTopTen.md +++ b/IndexTopTen.md @@ -60,7 +60,7 @@ This cheat sheet will help users of the [OWASP Top Ten](https://owasp.org/Top10/ - [Choosing and Using Security Questions Cheat Sheet](cheatsheets/Choosing_and_Using_Security_Questions_Cheat_Sheet.md) - [Credential Stuffing Prevention Cheat Sheet](cheatsheets/Credential_Stuffing_Prevention_Cheat_Sheet.md) - [Denial of Service Cheat Sheet](cheatsheets/Denial_of_Service_Cheat_Sheet.md) -- [Authentication Cheat Sheet (JWT)](cheatsheets/Authentication_Cheat_Sheet.md#json-web-tokens-jwt) +- [JSON Web Token Cheat Sheet](cheatsheets/JSON_Web_Token_Cheat_Sheet.md) - [Multifactor Authentication Cheat Sheet](cheatsheets/Multifactor_Authentication_Cheat_Sheet.md) - [Password Storage Cheat Sheet](cheatsheets/Password_Storage_Cheat_Sheet.md) - [SAML Security Cheat Sheet](cheatsheets/SAML_Security_Cheat_Sheet.md) diff --git a/cheatsheets/Authentication_Cheat_Sheet.md b/cheatsheets/Authentication_Cheat_Sheet.md index 30526bdc9f..50d5ec62cb 100644 --- a/cheatsheets/Authentication_Cheat_Sheet.md +++ b/cheatsheets/Authentication_Cheat_Sheet.md @@ -345,35 +345,9 @@ U2F augments password-based authentication using a hardware token (typically USB ### JSON Web Tokens (JWT) -JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are a compact, URL-safe way to represent claims between two parties. A JWT consists of three base64url-encoded parts separated by dots: a **header** (algorithm and token type), a **payload** (claims such as subject, issuer, expiration), and a **signature**. They are widely used as bearer tokens in authentication and authorization flows, including as OIDC ID tokens and OAuth 2.0 access tokens. +JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are widely used as bearer tokens in authentication and authorization flows, including as OIDC ID tokens and OAuth 2.0 access tokens. They are well-suited for stateless, cross-service authentication, but are not always the right choice — applications that require immediate session revocation on logout may find traditional server-side sessions simpler (see the [Session Management Cheat Sheet](Session_Management_Cheat_Sheet.md)). -#### When to Use (and Not Use) JWTs - -JWTs are well-suited for stateless authentication across distributed systems and for interoperating with standards such as OIDC and OAuth 2.0. However, they are not always the right choice: - -- **Use** JWTs when stateless, cross-service token passing is required or when following standards like OIDC or OAuth 2.0. -- **Avoid** JWTs as a drop-in replacement for server-side sessions in applications that need immediate revocation (e.g., on logout), unless a token denylist or short expiry with refresh tokens is in place. -- **Avoid** storing sensitive or secret data in JWT payloads — the payload is base64url-encoded, not encrypted by default. - -If your application does not need to be fully stateless, consider using traditional server-side sessions and follow the [Session Management Cheat Sheet](Session_Management_Cheat_Sheet.md). - -#### Common JWT Implementation Mistakes - -The following mistakes are frequently observed in JWT-based authentication implementations: - -- **Accepting the `none` algorithm**: Never accept tokens signed with `alg: none`. Always explicitly specify and enforce the expected signing algorithm server-side; do not allow the client to dictate which algorithm is used. -- **Skipping claim validation**: Always validate the `iss` (issuer), `aud` (audience), `exp` (expiration), and `nbf` (not before) claims. Failing to validate these is a common source of privilege escalation and token reuse vulnerabilities. -- **Missing or weak expiration**: Always set a short `exp` claim. Long-lived tokens extend the window of exploitation if compromised. Use short-lived access tokens paired with refresh tokens to balance security and usability. -- **Weak signing secrets**: HMAC-based JWTs (e.g., HS256) are only as secure as their signing secret. Use a cryptographically random secret of at least 256 bits. For distributed validation scenarios where multiple services verify tokens, prefer asymmetric algorithms such as RS256 or ES256 to avoid sharing a symmetric key across services. -- **Sensitive data in the payload**: JWT payloads are base64url-encoded, not encrypted. Do not store passwords, PII, secret keys, or other sensitive data in the payload. Use JSON Web Encryption (JWE, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516)) if payload confidentiality is required. -- **Insecure token storage**: Storing JWTs in `localStorage` or `sessionStorage` exposes them to cross-site scripting (XSS) attacks. Prefer HttpOnly cookies with `Secure` and `SameSite=Strict` flags, or use in-memory storage (e.g., JavaScript closures). If web storage must be used, enforce a strict Content Security Policy and apply token fingerprinting (see below). -- **Token sidejacking**: A stolen or intercepted token can be replayed by an attacker. Mitigate by binding the token to the user's browser session: generate a random fingerprint string at authentication time, store it in a hardened HttpOnly, Secure, SameSite=Strict cookie, and embed only its SHA-256 hash in the JWT payload. Validate the fingerprint on every request; any token replayed without the matching cookie must be rejected. -- **No token revocation**: JWTs remain valid until they expire; there is no built-in logout. Options include: (a) short expiry times with refresh token rotation; (b) a server-side token denylist that stores a SHA-256 digest of revoked tokens until their natural expiry; or (c) combining token fingerprinting with `sessionStorage` so that clearing storage effectively prevents further token use. - -#### Further Reading - -- [RFC 8725 — JSON Web Token Best Current Practices](https://datatracker.ietf.org/doc/html/rfc8725) -- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) — documents known JWT attacks and misconfigurations +For comprehensive guidance on JWT structure, common vulnerabilities, and secure implementation, see the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). ## Password Managers diff --git a/cheatsheets/Forgot_Password_Cheat_Sheet.md b/cheatsheets/Forgot_Password_Cheat_Sheet.md index a6d1d5f48b..0ae5fb28f2 100644 --- a/cheatsheets/Forgot_Password_Cheat_Sheet.md +++ b/cheatsheets/Forgot_Password_Cheat_Sheet.md @@ -63,7 +63,7 @@ These methods can be used together to provide a greater degree of assurance that It is essential to employ good security practices for the reset identifiers (tokens, codes, PINs, etc.). Some points don't apply to the [offline methods](#offline-methods), such as the lifetime restriction. All tokens and codes should be: - Generated using a [cryptographically secure random number generator](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). - - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [Authentication Cheat Sheet](Authentication_Cheat_Sheet.md#json-web-tokens-jwt). + - It is also possible to use JSON Web Tokens (JWTs) in place of random tokens, although this can introduce additional vulnerability, such as those discussed in the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md). - Long enough to protect against brute-force attacks. - Linked to an individual user in the database. - Invalidated after they have been used. diff --git a/cheatsheets/JSON_Web_Token_Cheat_Sheet.md b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md new file mode 100644 index 0000000000..5983a1225d --- /dev/null +++ b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md @@ -0,0 +1,629 @@ +# JSON Web Token Cheat Sheet + +## Introduction + +Many applications use **JSON Web Tokens** (JWT) to allow the client to indicate its identity for further exchange after authentication. + +From [JWT.IO](https://jwt.io/introduction): + +> JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA. + +JWTs are used to carry information related to the identity and characteristics (claims) of a client. This information is signed by the server to ensure it has not been tampered with after being sent to the client. This prevents an attacker from modifying the identity or characteristics — for example, changing the role from a simple user to an admin or altering the client's login. + +The token is created during authentication (it is issued upon successful authentication) and is verified by the server before any processing. Applications use the token to allow a client to present what is essentially an "identity card" to the server. The server can then securely verify the token's validity and integrity. This approach is stateless and portable, meaning it works across different client and server technologies, and over various transport channels — although HTTP is the most commonly used. + +## Token Structure + +Token structure example taken from [JWT.IO](https://jwt.io/#debugger): + +`[Base64(HEADER)].[Base64(PAYLOAD)].[Base64(SIGNATURE)]` + +```text +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. +eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. +TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ +``` + +Chunk 1: **Header** + +```json +{ + "alg": "HS256", + "typ": "JWT" +} +``` + +Chunk 2: **Payload** + +```json +{ + "sub": "1234567890", + "name": "John Doe", + "admin": true +} +``` + +Chunk 3: **Signature** + +```javascript +HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY ) +``` + +## Objective + +This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT). While the code examples use Java, the security concepts and recommendations apply to all languages and frameworks. + +The tips presented in this article are part of a Java project that was created to show the correct way to handle creation and validation of JSON Web Tokens. + +You can find the Java project [here](https://github.com/righettod/poc-jwt), it uses the official [JWT library](https://jwt.io/#libraries). + +In the rest of the article, the term **token** refers to the **JSON Web Tokens** (JWT). + +## Consideration about Using JWT + +Even if a JWT is "easy" to use and allow to expose services (mostly REST style) in a stateless way, it's not the solution that fits for all applications because it comes with some caveats, like for example the question of the storage of the token (tackled in this cheatsheet) and others... + +If your application does not need to be fully stateless, you can consider using traditional session system provided by all web frameworks and follow the advice from the dedicated [session management cheat sheet](Session_Management_Cheat_Sheet.md). However, for stateless applications, when well implemented, it's a good candidate. + +## Issues + +### None Hashing Algorithm + +#### Symptom + +This attack, described [here](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/), occurs when an attacker alters the token and changes the hashing algorithm to indicate, through the *none* keyword, that the integrity of the token has already been verified. As explained in the link above *some libraries treated tokens signed with the none algorithm as a valid token with a verified signature*, so an attacker can alter the token claims and the modified token will still be trusted by the application. + +#### How to Prevent + +First, use a JWT library that is not exposed to this vulnerability. + +Last, during token validation, explicitly request that the expected algorithm was used. + +#### Implementation Example + +``` java +// HMAC key - Block serialization and storage as String in JVM memory +private transient byte[] keyHMAC = ...; + +... + +//Create a verification context for the token requesting +//explicitly the use of the HMAC-256 hashing algorithm +JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)).build(); + +//Verify the token, if the verification fail then a exception is thrown +DecodedJWT decodedToken = verifier.verify(token); +``` + +### Token Sidejacking + +#### Symptom + +This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity. + +#### How to Prevent + +One way to prevent this is by adding a "user context" to the token. The user context should consist of the following: + +- A random string generated during the authentication phase. This string is sent to the client as a hardened cookie (with the following flags: [HttpOnly + Secure](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies), [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies), [Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie), and [cookie prefixes](https://googlechrome.github.io/samples/cookie-prefixes/)). Avoid setting the *expires* header so the cookie is cleared when the browser is closed. Set *Max-Age* to a value equal to or less than the JWT's expiry time — never more. +- A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing the attacker to read the random string value and setting the expected cookie. + +Avoid using IP addresses as part of the context. IP addresses can change during a single session due to legitimate reasons — for example, when a user accesses the application on a mobile device and switches network providers. Additionally, IP tracking can raise concerns related to [GDPR compliance](https://gdpr.eu/) in the EU. + +During token validation, if the received token does not contain the correct context (e.g., if it is being replayed by an attacker), it must be rejected. + +#### Implementation example + +Code to create the token after successful authentication. + +``` java +// HMAC key - Block serialization and storage as String in JVM memory +private transient byte[] keyHMAC = ...; +// Random data generator +private SecureRandom secureRandom = new SecureRandom(); + +... + +//Generate a random string that will constitute the fingerprint for this user +byte[] randomFgp = new byte[50]; +secureRandom.nextBytes(randomFgp); +String userFingerprint = DatatypeConverter.printHexBinary(randomFgp); + +//Add the fingerprint in a hardened cookie - Add cookie manually because +//SameSite attribute is not supported by javax.servlet.http.Cookie class +String fingerprintCookie = "__Secure-Fgp=" + userFingerprint + + "; SameSite=Strict; HttpOnly; Secure"; +response.addHeader("Set-Cookie", fingerprintCookie); + +//Compute a SHA256 hash of the fingerprint in order to store the +//fingerprint hash (instead of the raw value) in the token +//to prevent an XSS to be able to read the fingerprint and +//set the expected cookie itself +MessageDigest digest = MessageDigest.getInstance("SHA-256"); +byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); +String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); + +//Create the token with a validity of 15 minutes and client context (fingerprint) information +Calendar c = Calendar.getInstance(); +Date now = c.getTime(); +c.add(Calendar.MINUTE, 15); +Date expirationDate = c.getTime(); +Map headerClaims = new HashMap<>(); +headerClaims.put("typ", "JWT"); +String token = JWT.create().withSubject(login) + .withExpiresAt(expirationDate) + .withIssuer(this.issuerID) + .withIssuedAt(now) + .withNotBefore(now) + .withClaim("userFingerprint", userFingerprintHash) + .withHeader(headerClaims) + .sign(Algorithm.HMAC256(this.keyHMAC)); +``` + +Code to validate the token. + +``` java +// HMAC key - Block serialization and storage as String in JVM memory +private transient byte[] keyHMAC = ...; + +... + +//Retrieve the user fingerprint from the dedicated cookie +String userFingerprint = null; +if (request.getCookies() != null && request.getCookies().length > 0) { + List cookies = Arrays.stream(request.getCookies()).collect(Collectors.toList()); + Optional cookie = cookies.stream().filter(c -> "__Secure-Fgp" + .equals(c.getName())).findFirst(); + if (cookie.isPresent()) { + userFingerprint = cookie.get().getValue(); + } +} + +//Compute a SHA256 hash of the received fingerprint in cookie in order to compare +//it to the fingerprint hash stored in the token +MessageDigest digest = MessageDigest.getInstance("SHA-256"); +byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); +String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); + +//Create a verification context for the token +JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)) + .withIssuer(issuerID) + .withClaim("userFingerprint", userFingerprintHash) + .build(); + +//Verify the token, if the verification fail then an exception is thrown +DecodedJWT decodedToken = verifier.verify(token); +``` + +### No Built-In Token Revocation by the User + +#### Symptom + +This problem is inherent to JWT because a token only becomes invalid when it expires. The user has no built-in feature to explicitly revoke the validity of a token. This means that if it is stolen, a user cannot revoke the token itself thereby blocking the attacker. + +#### How to Prevent + +Since JWTs are stateless, There is no session maintained on the server(s) serving client requests. As such, there is no session to invalidate on the server side. A well implemented Token Sidejacking solution (as explained above) should alleviate the need for maintaining denylist on server side. This is because a hardened cookie used in the Token Sidejacking can be considered as secure as a session ID used in the traditional session system, and unless both the cookie and the JWT are intercepted/stolen, the JWT is unusable. A logout can thus be 'simulated' by clearing the JWT from session storage. If the user chooses to close the browser instead, then both the cookie and sessionStorage are cleared automatically. + +Another way to protect against this is to implement a token denylist that will be used to mimic the "logout" feature that exists with traditional session management system. + +The denylist will keep a digest (SHA-256 encoded in HEX) of the token with a revocation date. This entry must endure at least until the expiration of the token. + +When the user wants to "logout" then it call a dedicated service that will add the provided user token to the denylist resulting in an immediate invalidation of the token for further usage in the application. + +#### Implementation Example + +##### Block List Storage + +A database table with the following structure will be used as the central denylist storage. + +``` sql +create table if not exists revoked_token(jwt_token_digest varchar(255) primary key, +revocation_date timestamp default now()); +``` + +##### Token Revocation Management + +Code in charge of adding a token to the denylist and checking if a token is revoked. + +``` java +/** +* Handle the revocation of the token (logout). +* Use a DB in order to allow multiple instances to check for revoked token +* and allow cleanup at centralized DB level. +*/ +public class TokenRevoker { + + /** DB Connection */ + @Resource("jdbc/storeDS") + private DataSource storeDS; + + /** + * Verify if a digest encoded in HEX of the ciphered token is present + * in the revocation table + * + * @param jwtInHex Token encoded in HEX + * @return Presence flag + * @throws Exception If any issue occur during communication with DB + */ + public boolean isTokenRevoked(String jwtInHex) throws Exception { + boolean tokenIsPresent = false; + if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { + //Decode the ciphered token + byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); + + //Compute a SHA256 of the ciphered token + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] cipheredTokenDigest = digest.digest(cipheredToken); + String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); + + //Search token digest in HEX in DB + try (Connection con = this.storeDS.getConnection()) { + String query = "select jwt_token_digest from revoked_token where jwt_token_digest = ?"; + try (PreparedStatement pStatement = con.prepareStatement(query)) { + pStatement.setString(1, jwtTokenDigestInHex); + try (ResultSet rSet = pStatement.executeQuery()) { + tokenIsPresent = rSet.next(); + } + } + } + } + + return tokenIsPresent; + } + + + /** + * Add a digest encoded in HEX of the ciphered token to the revocation token table + * + * @param jwtInHex Token encoded in HEX + * @throws Exception If any issue occur during communication with DB + */ + public void revokeToken(String jwtInHex) throws Exception { + if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { + //Decode the ciphered token + byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); + + //Compute a SHA256 of the ciphered token + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] cipheredTokenDigest = digest.digest(cipheredToken); + String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); + + //Check if the token digest in HEX is already in the DB and add it if it is absent + if (!this.isTokenRevoked(jwtInHex)) { + try (Connection con = this.storeDS.getConnection()) { + String query = "insert into revoked_token(jwt_token_digest) values(?)"; + int insertedRecordCount; + try (PreparedStatement pStatement = con.prepareStatement(query)) { + pStatement.setString(1, jwtTokenDigestInHex); + insertedRecordCount = pStatement.executeUpdate(); + } + if (insertedRecordCount != 1) { + throw new IllegalStateException("Number of inserted record is invalid," + + " 1 expected but is " + insertedRecordCount); + } + } + } + + } + } +``` + +### Token Information Disclosure + +#### Symptom + +This attack occurs when an attacker has access to a token (or a set of tokens) and extracts information stored in it (the contents of JWTs are base64 encoded, but is not encrypted by default) in order to obtain information about the system. Information can be for example the security roles, login format... + +#### How to Prevent + +A way to protect against this attack is to cipher the token using, for example, a symmetric algorithm. + +It's also important to protect the ciphered data against attack like [Padding Oracle](https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/02-Testing_for_Padding_Oracle.html) or any other attack using cryptanalysis. + +In order to achieve all these goals, the *AES-[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode)* algorithm is used which provides *Authenticated Encryption with Associated Data*. + +More details from [here](https://github.com/google/tink/blob/master/docs/PRIMITIVES.md#deterministic-authenticated-encryption-with-associated-data): + +```text +AEAD primitive (Authenticated Encryption with Associated Data) provides functionality of symmetric +authenticated encryption. + +Implementations of this primitive are secure against adaptive chosen ciphertext attacks. + +When encrypting a plaintext one can optionally provide associated data that should be authenticated +but not encrypted. + +That is, the encryption with associated data ensures authenticity (ie. who the sender is) and +integrity (ie. data has not been tampered with) of that data, but not its secrecy. + +See RFC5116: https://tools.ietf.org/html/rfc5116 +``` + +**Note:** + +Here ciphering is added mainly to hide internal information but it's very important to remember that the first protection against tampering of the JWT is the signature. So, the token signature and its verification must be always in place. + +#### Implementation Example + +##### Token Ciphering + +Code in charge of managing the ciphering. [Google Tink](https://github.com/google/tink) dedicated crypto library is used to handle ciphering operations in order to use built-in best practices provided by this library. + +``` java +/** + * Handle ciphering and deciphering of the token using AES-GCM. + * + * @see "https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md" + */ +public class TokenCipher { + + /** + * Constructor - Register AEAD configuration + * + * @throws Exception If any issue occur during AEAD configuration registration + */ + public TokenCipher() throws Exception { + AeadConfig.register(); + } + + /** + * Cipher a JWT + * + * @param jwt Token to cipher + * @param keysetHandle Pointer to the keyset handle + * @return The ciphered version of the token encoded in HEX + * @throws Exception If any issue occur during token ciphering operation + */ + public String cipherToken(String jwt, KeysetHandle keysetHandle) throws Exception { + //Verify parameters + if (jwt == null || jwt.isEmpty() || keysetHandle == null) { + throw new IllegalArgumentException("Both parameters must be specified!"); + } + + //Get the primitive + Aead aead = AeadFactory.getPrimitive(keysetHandle); + + //Cipher the token + byte[] cipheredToken = aead.encrypt(jwt.getBytes(), null); + + return DatatypeConverter.printHexBinary(cipheredToken); + } + + /** + * Decipher a JWT + * + * @param jwtInHex Token to decipher encoded in HEX + * @param keysetHandle Pointer to the keyset handle + * @return The token in clear text + * @throws Exception If any issue occur during token deciphering operation + */ + public String decipherToken(String jwtInHex, KeysetHandle keysetHandle) throws Exception { + //Verify parameters + if (jwtInHex == null || jwtInHex.isEmpty() || keysetHandle == null) { + throw new IllegalArgumentException("Both parameters must be specified !"); + } + + //Decode the ciphered token + byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); + + //Get the primitive + Aead aead = AeadFactory.getPrimitive(keysetHandle); + + //Decipher the token + byte[] decipheredToken = aead.decrypt(cipheredToken, null); + + return new String(decipheredToken); + } +} +``` + +##### Creation / Validation of the Token + +Use the token ciphering handler during the creation and the validation of the token. + +Load keys (ciphering key was generated and stored using [Google Tink](https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md#generating-new-keysets)) and setup cipher. + +``` java +//Load keys from configuration text/json files in order to avoid to storing keys as a String in JVM memory +private transient byte[] keyHMAC = Files.readAllBytes(Paths.get("src", "main", "conf", "key-hmac.txt")); +private transient KeysetHandle keyCiphering = CleartextKeysetHandle.read(JsonKeysetReader.withFile( +Paths.get("src", "main", "conf", "key-ciphering.json").toFile())); + +... + +//Init token ciphering handler +TokenCipher tokenCipher = new TokenCipher(); +``` + +Token creation. + +``` java +//Generate the JWT token using the JWT API... +//Cipher the token (String JSON representation) +String cipheredToken = tokenCipher.cipherToken(token, this.keyCiphering); +//Send the ciphered token encoded in HEX to the client in HTTP response... +``` + +Token validation. + +``` java +//Retrieve the ciphered token encoded in HEX from the HTTP request... +//Decipher the token +String token = tokenCipher.decipherToken(cipheredToken, this.keyCiphering); +//Verify the token using the JWT API... +//Verify access... +``` + +### Token Storage on Client Side + +#### Symptom + +This occurs when an application stores the token in a manner exhibiting the following behavior: + +- Automatically sent by the browser (*Cookie* storage). +- Retrieved even if the browser is restarted (Use of browser *localStorage* container). +- Retrieved in case of [XSS](Cross_Site_Scripting_Prevention_Cheat_Sheet.md) issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage). + +#### How to Prevent + +1. Store the token using the browser *sessionStorage* container, or use JavaScript *closures* with *private* variables +1. Add it as a *Bearer* HTTP `Authentication` header with JavaScript when calling services. +1. Add [fingerprint](#token-sidejacking) information to the token. + +By storing the token in browser *sessionStorage* container it exposes the token to being stolen through an XSS attack. However, fingerprints added to the token prevent reuse of the stolen token by the attacker on their machine. To close a maximum of exploitation surfaces for an attacker, add a browser [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) to harden the execution context. + +But, we know that *sessionStorage* is not always practical due to its per-tab scope, and the storage method for tokens should balance *security* and *usability*. + +*LocalStorage* is a better method than *sessionStorage* for usability because it allows the session to persist between browser restarts and across tabs, but you must use strict security controls: + +- Tokens stored in *localStorage* should have *short expiration times* (e.g., *15-30 minutes idle timeout, 8-hour absolute timeout*). +- Implement mechanisms such as *token rotation* and *refresh tokens* to minimize risk. + +If *session persistence across tabs* and *sessionStorage* are required, consider using *BroadcastChannel API* or *Single Sign-On (SSO)* to re-authenticate users automatically when they open new tabs. + +An alternative to storing token in browser *sessionStorage* or in *localStorage* is to use JavaScript private variable or Closures. In this, access to all web requests are routed through a JavaScript module that encapsulates the token in a private variable which can not be accessed other than from within the module. + +*Note:* + +- The remaining case is when an attacker uses the user's browsing context as a proxy to use the target application through the legitimate user but the Content Security Policy can prevent communication with non expected domains. +- It's also possible to implement the authentication service in a way that the token is issued within a hardened cookie, but in this case, protection against a [Cross-Site Request Forgery](Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) attack must be implemented. + +#### Implementation Example + +JavaScript code to store the token after authentication. + +``` javascript +/* Handle request for JWT token and local storage*/ +function authenticate() { + const login = $("#login").val(); + const postData = "login=" + encodeURIComponent(login) + "&password=test"; + + $.post("/services/authenticate", postData, function (data) { + if (data.status == "Authentication successful!") { + ... + sessionStorage.setItem("token", data.token); + } + else { + ... + sessionStorage.removeItem("token"); + } + }) + .fail(function (jqXHR, textStatus, error) { + ... + sessionStorage.removeItem("token"); + }); +} +``` + +JavaScript code to add the token as a *Bearer* HTTP Authentication header when calling a service, for example a service to validate token here. + +``` javascript +/* Handle request for JWT token validation */ +function validateToken() { + var token = sessionStorage.getItem("token"); + + if (token == undefined || token == "") { + $("#infoZone").removeClass(); + $("#infoZone").addClass("alert alert-warning"); + $("#infoZone").text("Obtain a JWT token first :)"); + return; + } + + $.ajax({ + url: "/services/validate", + type: "POST", + beforeSend: function (xhr) { + xhr.setRequestHeader("Authorization", "bearer " + token); + }, + success: function (data) { + ... + }, + error: function (jqXHR, textStatus, error) { + ... + }, + }); +} +``` + +JavaScript code to implement closures with private variables: + +``` javascript +function myFetchModule() { + // Protect the original 'fetch' from getting overwritten via XSS + const fetch = window.fetch; + + const authOrigins = ["https://yourorigin", "http://localhost"]; + let token = ''; + + this.setToken = (value) => { + token = value + } + + this.fetch = (resource, options) => { + let req = new Request(resource, options); + destOrigin = new URL(req.url).origin; + if (token && authOrigins.includes(destOrigin)) { + req.headers.set('Authorization', token); + } + return fetch(req) + } +} + +... + +// usage: +const myFetch = new myFetchModule() + +function login() { + fetch("/api/login") + .then((res) => { + if (res.status == 200) { + return res.json() + } else { + throw Error(res.statusText) + } + }) + .then(data => { + myFetch.setToken(data.token) + console.log("Token received and stored.") + }) + .catch(console.error) +} + +... + +// after login, subsequent api calls: +function makeRequest() { + myFetch.fetch("/api/hello", {headers: {"MyHeader": "foobar"}}) + .then((res) => { + if (res.status == 200) { + return res.text() + } else { + throw Error(res.statusText) + } + }).then(responseText => console.log("helloResponse", responseText)) + .catch(console.error) +} +``` + +### Weak Token Secret + +#### Symptom + +When the token is protected using an HMAC based algorithm, the security of the token is entirely dependent on the strength of the secret used with the HMAC. If an attacker can obtain a valid JWT, they can then carry out an offline attack and attempt to crack the secret using tools such as [John the Ripper](https://github.com/magnumripper/JohnTheRipper) or [Hashcat](https://github.com/hashcat/hashcat). + +If they are successful, they would then be able to modify the token and re-sign it with the key they had obtained. This could let them escalate their privileges, compromise other users' accounts, or perform other actions depending on the contents of the JWT. + +There are a number of [guides](https://www.notsosecure.com/crafting-way-json-web-tokens/) that document this process in greater detail. + +#### How to Prevent + +The simplest way to prevent this attack is to ensure that the secret used to sign the JWTs is strong and unique, in order to make it harder for an attacker to crack. As this secret would never need to be typed by a human, it should be at least 64 characters, and generated using a [secure source of randomness](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). + +Alternatively, consider the use of tokens that are signed with RSA rather than using an HMAC and secret key. + +#### Further Reading + +- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) - A project documents the known attacks and potential security vulnerabilities and misconfigurations of JSON Web Tokens. +- [JWT Best Practices Internet Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-jwt-bcp/) diff --git a/cheatsheets/REST_Security_Cheat_Sheet.md b/cheatsheets/REST_Security_Cheat_Sheet.md index 8df3f953ad..71eda89d68 100644 --- a/cheatsheets/REST_Security_Cheat_Sheet.md +++ b/cheatsheets/REST_Security_Cheat_Sheet.md @@ -56,7 +56,7 @@ Some claims have been standardized and should be present in JWT used for access - `exp` or expiration time - is the current time before the end of the validity period of this token? - `nbf` or not before time - is the current time after the start of the validity period of this token? -As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [Authentication Cheat Sheet](Authentication_Cheat_Sheet.md#json-web-tokens-jwt) for further details. +As JWTs contain details of the authenticated entity (user etc.) a disconnect can occur between the JWT and the current state of the users session, for example, if the session is terminated earlier than the expiration time due to an explicit logout or an idle timeout. When an explicit session termination event occurs, a digest or hash of any associated JWTs should be submitted to a denylist on the API which will invalidate that JWT for any requests until the expiration of the token. See the [JSON Web Token Cheat Sheet](JSON_Web_Token_Cheat_Sheet.md#no-built-in-token-revocation-by-the-user) for further details. ## API Keys From 152b7de6c55205cf1a6d29ee6fac8ba5d04bbb28 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 20:30:27 +0000 Subject: [PATCH 5/5] Rewrite JSON_Web_Token_Cheat_Sheet as concise, language-agnostic security guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all Java code examples (629 lines → 164 lines) - Restructure around 8 security vulnerability categories with risk/mitigation format - Cover: alg:none, RS256→HS256 confusion, weak secrets, missing claims validation, sensitive data in payload, insecure storage, token sidejacking, lack of revocation, header injection (kid/jku/x5u) - Add Best Practices Summary and References sections - All markdownlint checks pass Agent-Logs-Url: https://github.com/OWASP/CheatSheetSeries/sessions/a7188aba-1cb6-4b4f-a4c4-cf5fb6cc048a Co-authored-by: mackowski <35339942+mackowski@users.noreply.github.com> --- cheatsheets/JSON_Web_Token_Cheat_Sheet.md | 661 ++++------------------ 1 file changed, 98 insertions(+), 563 deletions(-) diff --git a/cheatsheets/JSON_Web_Token_Cheat_Sheet.md b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md index 5983a1225d..a07d5015f8 100644 --- a/cheatsheets/JSON_Web_Token_Cheat_Sheet.md +++ b/cheatsheets/JSON_Web_Token_Cheat_Sheet.md @@ -2,29 +2,13 @@ ## Introduction -Many applications use **JSON Web Tokens** (JWT) to allow the client to indicate its identity for further exchange after authentication. +JSON Web Tokens (JWT, [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) are a compact, URL-safe means of representing claims to be transferred between two parties. They are widely used as bearer tokens in authentication and authorization flows — including as OIDC ID tokens and OAuth 2.0 access tokens. -From [JWT.IO](https://jwt.io/introduction): +A JWT consists of three Base64URL-encoded sections separated by dots: -> JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA. +`[Base64URL(HEADER)].[Base64URL(PAYLOAD)].[Base64URL(SIGNATURE)]` -JWTs are used to carry information related to the identity and characteristics (claims) of a client. This information is signed by the server to ensure it has not been tampered with after being sent to the client. This prevents an attacker from modifying the identity or characteristics — for example, changing the role from a simple user to an admin or altering the client's login. - -The token is created during authentication (it is issued upon successful authentication) and is verified by the server before any processing. Applications use the token to allow a client to present what is essentially an "identity card" to the server. The server can then securely verify the token's validity and integrity. This approach is stateless and portable, meaning it works across different client and server technologies, and over various transport channels — although HTTP is the most commonly used. - -## Token Structure - -Token structure example taken from [JWT.IO](https://jwt.io/#debugger): - -`[Base64(HEADER)].[Base64(PAYLOAD)].[Base64(SIGNATURE)]` - -```text -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. -eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9. -TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ -``` - -Chunk 1: **Header** +**Header** — identifies the token type and signing algorithm: ```json { @@ -33,597 +17,148 @@ Chunk 1: **Header** } ``` -Chunk 2: **Payload** +**Payload** — contains claims (statements about the subject and metadata): ```json { "sub": "1234567890", "name": "John Doe", - "admin": true -} -``` - -Chunk 3: **Signature** - -```javascript -HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), KEY ) -``` - -## Objective - -This cheat sheet provides tips to prevent common security issues when using JSON Web Tokens (JWT). While the code examples use Java, the security concepts and recommendations apply to all languages and frameworks. - -The tips presented in this article are part of a Java project that was created to show the correct way to handle creation and validation of JSON Web Tokens. - -You can find the Java project [here](https://github.com/righettod/poc-jwt), it uses the official [JWT library](https://jwt.io/#libraries). - -In the rest of the article, the term **token** refers to the **JSON Web Tokens** (JWT). - -## Consideration about Using JWT - -Even if a JWT is "easy" to use and allow to expose services (mostly REST style) in a stateless way, it's not the solution that fits for all applications because it comes with some caveats, like for example the question of the storage of the token (tackled in this cheatsheet) and others... - -If your application does not need to be fully stateless, you can consider using traditional session system provided by all web frameworks and follow the advice from the dedicated [session management cheat sheet](Session_Management_Cheat_Sheet.md). However, for stateless applications, when well implemented, it's a good candidate. - -## Issues - -### None Hashing Algorithm - -#### Symptom - -This attack, described [here](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/), occurs when an attacker alters the token and changes the hashing algorithm to indicate, through the *none* keyword, that the integrity of the token has already been verified. As explained in the link above *some libraries treated tokens signed with the none algorithm as a valid token with a verified signature*, so an attacker can alter the token claims and the modified token will still be trusted by the application. - -#### How to Prevent - -First, use a JWT library that is not exposed to this vulnerability. - -Last, during token validation, explicitly request that the expected algorithm was used. - -#### Implementation Example - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; - -... - -//Create a verification context for the token requesting -//explicitly the use of the HMAC-256 hashing algorithm -JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)).build(); - -//Verify the token, if the verification fail then a exception is thrown -DecodedJWT decodedToken = verifier.verify(token); -``` - -### Token Sidejacking - -#### Symptom - -This attack occurs when a token has been intercepted/stolen by an attacker and they use it to gain access to the system using targeted user identity. - -#### How to Prevent - -One way to prevent this is by adding a "user context" to the token. The user context should consist of the following: - -- A random string generated during the authentication phase. This string is sent to the client as a hardened cookie (with the following flags: [HttpOnly + Secure](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Secure_and_HttpOnly_cookies), [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies), [Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie), and [cookie prefixes](https://googlechrome.github.io/samples/cookie-prefixes/)). Avoid setting the *expires* header so the cookie is cleared when the browser is closed. Set *Max-Age* to a value equal to or less than the JWT's expiry time — never more. -- A SHA256 hash of the random string will be stored in the token (instead of the raw value) in order to prevent any XSS issues allowing the attacker to read the random string value and setting the expected cookie. - -Avoid using IP addresses as part of the context. IP addresses can change during a single session due to legitimate reasons — for example, when a user accesses the application on a mobile device and switches network providers. Additionally, IP tracking can raise concerns related to [GDPR compliance](https://gdpr.eu/) in the EU. - -During token validation, if the received token does not contain the correct context (e.g., if it is being replayed by an attacker), it must be rejected. - -#### Implementation example - -Code to create the token after successful authentication. - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; -// Random data generator -private SecureRandom secureRandom = new SecureRandom(); - -... - -//Generate a random string that will constitute the fingerprint for this user -byte[] randomFgp = new byte[50]; -secureRandom.nextBytes(randomFgp); -String userFingerprint = DatatypeConverter.printHexBinary(randomFgp); - -//Add the fingerprint in a hardened cookie - Add cookie manually because -//SameSite attribute is not supported by javax.servlet.http.Cookie class -String fingerprintCookie = "__Secure-Fgp=" + userFingerprint - + "; SameSite=Strict; HttpOnly; Secure"; -response.addHeader("Set-Cookie", fingerprintCookie); - -//Compute a SHA256 hash of the fingerprint in order to store the -//fingerprint hash (instead of the raw value) in the token -//to prevent an XSS to be able to read the fingerprint and -//set the expected cookie itself -MessageDigest digest = MessageDigest.getInstance("SHA-256"); -byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); -String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); - -//Create the token with a validity of 15 minutes and client context (fingerprint) information -Calendar c = Calendar.getInstance(); -Date now = c.getTime(); -c.add(Calendar.MINUTE, 15); -Date expirationDate = c.getTime(); -Map headerClaims = new HashMap<>(); -headerClaims.put("typ", "JWT"); -String token = JWT.create().withSubject(login) - .withExpiresAt(expirationDate) - .withIssuer(this.issuerID) - .withIssuedAt(now) - .withNotBefore(now) - .withClaim("userFingerprint", userFingerprintHash) - .withHeader(headerClaims) - .sign(Algorithm.HMAC256(this.keyHMAC)); -``` - -Code to validate the token. - -``` java -// HMAC key - Block serialization and storage as String in JVM memory -private transient byte[] keyHMAC = ...; - -... - -//Retrieve the user fingerprint from the dedicated cookie -String userFingerprint = null; -if (request.getCookies() != null && request.getCookies().length > 0) { - List cookies = Arrays.stream(request.getCookies()).collect(Collectors.toList()); - Optional cookie = cookies.stream().filter(c -> "__Secure-Fgp" - .equals(c.getName())).findFirst(); - if (cookie.isPresent()) { - userFingerprint = cookie.get().getValue(); - } + "role": "admin", + "exp": 1716239022 } - -//Compute a SHA256 hash of the received fingerprint in cookie in order to compare -//it to the fingerprint hash stored in the token -MessageDigest digest = MessageDigest.getInstance("SHA-256"); -byte[] userFingerprintDigest = digest.digest(userFingerprint.getBytes("utf-8")); -String userFingerprintHash = DatatypeConverter.printHexBinary(userFingerprintDigest); - -//Create a verification context for the token -JWTVerifier verifier = JWT.require(Algorithm.HMAC256(keyHMAC)) - .withIssuer(issuerID) - .withClaim("userFingerprint", userFingerprintHash) - .build(); - -//Verify the token, if the verification fail then an exception is thrown -DecodedJWT decodedToken = verifier.verify(token); -``` - -### No Built-In Token Revocation by the User - -#### Symptom - -This problem is inherent to JWT because a token only becomes invalid when it expires. The user has no built-in feature to explicitly revoke the validity of a token. This means that if it is stolen, a user cannot revoke the token itself thereby blocking the attacker. - -#### How to Prevent - -Since JWTs are stateless, There is no session maintained on the server(s) serving client requests. As such, there is no session to invalidate on the server side. A well implemented Token Sidejacking solution (as explained above) should alleviate the need for maintaining denylist on server side. This is because a hardened cookie used in the Token Sidejacking can be considered as secure as a session ID used in the traditional session system, and unless both the cookie and the JWT are intercepted/stolen, the JWT is unusable. A logout can thus be 'simulated' by clearing the JWT from session storage. If the user chooses to close the browser instead, then both the cookie and sessionStorage are cleared automatically. - -Another way to protect against this is to implement a token denylist that will be used to mimic the "logout" feature that exists with traditional session management system. - -The denylist will keep a digest (SHA-256 encoded in HEX) of the token with a revocation date. This entry must endure at least until the expiration of the token. - -When the user wants to "logout" then it call a dedicated service that will add the provided user token to the denylist resulting in an immediate invalidation of the token for further usage in the application. - -#### Implementation Example - -##### Block List Storage - -A database table with the following structure will be used as the central denylist storage. - -``` sql -create table if not exists revoked_token(jwt_token_digest varchar(255) primary key, -revocation_date timestamp default now()); ``` -##### Token Revocation Management - -Code in charge of adding a token to the denylist and checking if a token is revoked. - -``` java -/** -* Handle the revocation of the token (logout). -* Use a DB in order to allow multiple instances to check for revoked token -* and allow cleanup at centralized DB level. -*/ -public class TokenRevoker { - - /** DB Connection */ - @Resource("jdbc/storeDS") - private DataSource storeDS; - - /** - * Verify if a digest encoded in HEX of the ciphered token is present - * in the revocation table - * - * @param jwtInHex Token encoded in HEX - * @return Presence flag - * @throws Exception If any issue occur during communication with DB - */ - public boolean isTokenRevoked(String jwtInHex) throws Exception { - boolean tokenIsPresent = false; - if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Compute a SHA256 of the ciphered token - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] cipheredTokenDigest = digest.digest(cipheredToken); - String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); - - //Search token digest in HEX in DB - try (Connection con = this.storeDS.getConnection()) { - String query = "select jwt_token_digest from revoked_token where jwt_token_digest = ?"; - try (PreparedStatement pStatement = con.prepareStatement(query)) { - pStatement.setString(1, jwtTokenDigestInHex); - try (ResultSet rSet = pStatement.executeQuery()) { - tokenIsPresent = rSet.next(); - } - } - } - } - - return tokenIsPresent; - } - - - /** - * Add a digest encoded in HEX of the ciphered token to the revocation token table - * - * @param jwtInHex Token encoded in HEX - * @throws Exception If any issue occur during communication with DB - */ - public void revokeToken(String jwtInHex) throws Exception { - if (jwtInHex != null && !jwtInHex.trim().isEmpty()) { - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Compute a SHA256 of the ciphered token - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] cipheredTokenDigest = digest.digest(cipheredToken); - String jwtTokenDigestInHex = DatatypeConverter.printHexBinary(cipheredTokenDigest); - - //Check if the token digest in HEX is already in the DB and add it if it is absent - if (!this.isTokenRevoked(jwtInHex)) { - try (Connection con = this.storeDS.getConnection()) { - String query = "insert into revoked_token(jwt_token_digest) values(?)"; - int insertedRecordCount; - try (PreparedStatement pStatement = con.prepareStatement(query)) { - pStatement.setString(1, jwtTokenDigestInHex); - insertedRecordCount = pStatement.executeUpdate(); - } - if (insertedRecordCount != 1) { - throw new IllegalStateException("Number of inserted record is invalid," + - " 1 expected but is " + insertedRecordCount); - } - } - } - - } - } -``` +**Signature** — ensures the token has not been tampered with (e.g., `HMACSHA256(base64url(header) + "." + base64url(payload), secret)`). -### Token Information Disclosure +> **When not to use JWTs:** JWTs are stateless by design — once issued they are valid until expiry. Applications that require immediate session revocation on logout (e.g., banking, healthcare) may find traditional server-side sessions simpler. See the [Session Management Cheat Sheet](Session_Management_Cheat_Sheet.md). -#### Symptom +## Security Vulnerabilities and Mitigations -This attack occurs when an attacker has access to a token (or a set of tokens) and extracts information stored in it (the contents of JWTs are base64 encoded, but is not encrypted by default) in order to obtain information about the system. Information can be for example the security roles, login format... +### 1. Algorithm Confusion (`alg: none` and RS256 → HS256) -#### How to Prevent +**Risk:** Attackers may manipulate the `alg` header to bypass signature verification. -A way to protect against this attack is to cipher the token using, for example, a symmetric algorithm. +- **`alg: none`** — Some libraries accepted tokens with algorithm set to `none`, treating them as already-verified. An attacker can strip the signature entirely and forge any claims. +- **RS256 → HS256 confusion** — If a server uses RS256 (asymmetric), an attacker can change `alg` to HS256 and re-sign the token using the **public key** as the HMAC secret, which the server may inadvertently accept. -It's also important to protect the ciphered data against attack like [Padding Oracle](https://owasp.org/www-project-web-security-testing-guide/stable/4-Web_Application_Security_Testing/09-Testing_for_Weak_Cryptography/02-Testing_for_Padding_Oracle.html) or any other attack using cryptanalysis. +**Mitigations:** -In order to achieve all these goals, the *AES-[GCM](https://en.wikipedia.org/wiki/Galois/Counter_Mode)* algorithm is used which provides *Authenticated Encryption with Associated Data*. +- Always explicitly specify and enforce the **expected algorithm** in your validation code — never derive it from the token header alone. +- Use an **allowlist** of accepted algorithms; reject anything not on it, especially `none`. +- Use an up-to-date, well-maintained JWT library and check it is not vulnerable to these issues (see [jwt.io/libraries](https://jwt.io/libraries)). -More details from [here](https://github.com/google/tink/blob/master/docs/PRIMITIVES.md#deterministic-authenticated-encryption-with-associated-data): +### 2. Weak Signing Secrets -```text -AEAD primitive (Authenticated Encryption with Associated Data) provides functionality of symmetric -authenticated encryption. +**Risk:** HMAC-based tokens (HS256/384/512) are only as secure as their signing secret. An attacker who obtains a valid JWT can perform an offline brute-force attack using tools such as [Hashcat](https://hashcat.net/) or [John the Ripper](https://github.com/openwall/john) to recover the secret, then forge arbitrary tokens. -Implementations of this primitive are secure against adaptive chosen ciphertext attacks. +**Mitigations:** -When encrypting a plaintext one can optionally provide associated data that should be authenticated -but not encrypted. +- Use a **secret of at least 64 random characters** (256+ bits of entropy) — never use a human-memorable passphrase. +- Generate the secret with a [cryptographically secure random number generator](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). +- Prefer **asymmetric algorithms** (RS256, ES256, PS256) so the signing key is never shared or exposed. +- Rotate secrets/keys periodically and after any suspected compromise. -That is, the encryption with associated data ensures authenticity (ie. who the sender is) and -integrity (ie. data has not been tampered with) of that data, but not its secrecy. +### 3. Missing or Insufficient Claims Validation -See RFC5116: https://tools.ietf.org/html/rfc5116 -``` +**Risk:** Failing to validate standard claims allows attackers to replay expired tokens, use tokens issued for different audiences or issuers, or use tokens before they are valid. -**Note:** - -Here ciphering is added mainly to hide internal information but it's very important to remember that the first protection against tampering of the JWT is the signature. So, the token signature and its verification must be always in place. - -#### Implementation Example - -##### Token Ciphering - -Code in charge of managing the ciphering. [Google Tink](https://github.com/google/tink) dedicated crypto library is used to handle ciphering operations in order to use built-in best practices provided by this library. - -``` java -/** - * Handle ciphering and deciphering of the token using AES-GCM. - * - * @see "https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md" - */ -public class TokenCipher { - - /** - * Constructor - Register AEAD configuration - * - * @throws Exception If any issue occur during AEAD configuration registration - */ - public TokenCipher() throws Exception { - AeadConfig.register(); - } - - /** - * Cipher a JWT - * - * @param jwt Token to cipher - * @param keysetHandle Pointer to the keyset handle - * @return The ciphered version of the token encoded in HEX - * @throws Exception If any issue occur during token ciphering operation - */ - public String cipherToken(String jwt, KeysetHandle keysetHandle) throws Exception { - //Verify parameters - if (jwt == null || jwt.isEmpty() || keysetHandle == null) { - throw new IllegalArgumentException("Both parameters must be specified!"); - } - - //Get the primitive - Aead aead = AeadFactory.getPrimitive(keysetHandle); - - //Cipher the token - byte[] cipheredToken = aead.encrypt(jwt.getBytes(), null); - - return DatatypeConverter.printHexBinary(cipheredToken); - } - - /** - * Decipher a JWT - * - * @param jwtInHex Token to decipher encoded in HEX - * @param keysetHandle Pointer to the keyset handle - * @return The token in clear text - * @throws Exception If any issue occur during token deciphering operation - */ - public String decipherToken(String jwtInHex, KeysetHandle keysetHandle) throws Exception { - //Verify parameters - if (jwtInHex == null || jwtInHex.isEmpty() || keysetHandle == null) { - throw new IllegalArgumentException("Both parameters must be specified !"); - } - - //Decode the ciphered token - byte[] cipheredToken = DatatypeConverter.parseHexBinary(jwtInHex); - - //Get the primitive - Aead aead = AeadFactory.getPrimitive(keysetHandle); - - //Decipher the token - byte[] decipheredToken = aead.decrypt(cipheredToken, null); - - return new String(decipheredToken); - } -} -``` +**Mitigations:** Always validate the following claims on every request: -##### Creation / Validation of the Token +| Claim | Meaning | Validation | +| ------- | --------- | ---------- | +| `exp` | Expiration time | Reject tokens past this time | +| `nbf` | Not before | Reject tokens before this time | +| `iat` | Issued at | Optionally enforce a max token age | +| `iss` | Issuer | Must match the expected issuer | +| `aud` | Audience | Must match your service's identifier | +| `sub` | Subject | Must correspond to a valid, active user | -Use the token ciphering handler during the creation and the validation of the token. +Use short **expiration times** — 15 minutes is common for access tokens. Issue a separate, longer-lived refresh token to obtain new access tokens. -Load keys (ciphering key was generated and stored using [Google Tink](https://github.com/google/tink/blob/master/docs/JAVA-HOWTO.md#generating-new-keysets)) and setup cipher. +### 4. Sensitive Data in Payload -``` java -//Load keys from configuration text/json files in order to avoid to storing keys as a String in JVM memory -private transient byte[] keyHMAC = Files.readAllBytes(Paths.get("src", "main", "conf", "key-hmac.txt")); -private transient KeysetHandle keyCiphering = CleartextKeysetHandle.read(JsonKeysetReader.withFile( -Paths.get("src", "main", "conf", "key-ciphering.json").toFile())); +**Risk:** JWT payloads are Base64URL-encoded, **not encrypted**. Anyone who intercepts or decodes a token can read the payload. Embedding PII, passwords, or internal implementation details exposes sensitive information. -... +**Mitigations:** -//Init token ciphering handler -TokenCipher tokenCipher = new TokenCipher(); -``` +- Store only the **minimum necessary claims** (e.g., user ID, roles, expiry). +- Never store passwords, secrets, or sensitive PII in JWT payload. +- If payload confidentiality is required, use **JSON Web Encryption (JWE, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))** which encrypts the payload, or encrypt sensitive fields before embedding them. Use an authenticated encryption algorithm such as AES-GCM to prevent padding oracle and other cryptanalysis attacks. +- Always transmit tokens over **TLS/HTTPS**. -Token creation. +### 5. Insecure Token Storage (Client Side) -``` java -//Generate the JWT token using the JWT API... -//Cipher the token (String JSON representation) -String cipheredToken = tokenCipher.cipherToken(token, this.keyCiphering); -//Send the ciphered token encoded in HEX to the client in HTTP response... -``` +**Risk:** Where and how a JWT is stored in the browser determines the attack surface: -Token validation. +- **`localStorage`** — Persists across sessions and tabs; accessible to any JavaScript on the page, making it vulnerable to [XSS](Cross_Site_Scripting_Prevention_Cheat_Sheet.md) attacks. +- **`sessionStorage`** — Cleared when the tab is closed; still accessible to JavaScript on the page. +- **Accessible cookies** — If not hardened, vulnerable to [CSRF](Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) and XSS. -``` java -//Retrieve the ciphered token encoded in HEX from the HTTP request... -//Decipher the token -String token = tokenCipher.decipherToken(cipheredToken, this.keyCiphering); -//Verify the token using the JWT API... -//Verify access... -``` +**Mitigations:** -### Token Storage on Client Side +- Store access tokens in **memory (JavaScript variable/closure)** where possible; they disappear on page reload, limiting exposure. +- If persistence is needed, use `sessionStorage` with a **short-lived token** and refresh-token rotation. +- If storing in a cookie, use `HttpOnly; Secure; SameSite=Strict` flags and implement CSRF protection. +- Implement a strict [Content Security Policy](Content_Security_Policy_Cheat_Sheet.md) to reduce the XSS attack surface. +- Apply a **token fingerprint** (see [Token Sidejacking](#6-token-sidejacking)) to limit the usefulness of stolen tokens. -#### Symptom +### 6. Token Sidejacking -This occurs when an application stores the token in a manner exhibiting the following behavior: +**Risk:** If a token is stolen (via XSS, network interception, or log exposure), an attacker can use it from their own machine until it expires. -- Automatically sent by the browser (*Cookie* storage). -- Retrieved even if the browser is restarted (Use of browser *localStorage* container). -- Retrieved in case of [XSS](Cross_Site_Scripting_Prevention_Cheat_Sheet.md) issue (Cookie accessible to JavaScript code or Token stored in browser local/session storage). +**Mitigation — Token Fingerprinting:** -#### How to Prevent +1. At authentication time, generate a cryptographically random string (the "fingerprint"). +2. Send the raw fingerprint to the client as a hardened cookie: `HttpOnly; Secure; SameSite=Strict` with `Max-Age` equal to or less than the token expiry. +3. Store a **SHA-256 hash** of the fingerprint (not the raw value) as a claim inside the JWT. +4. On every request, re-hash the cookie value and compare it to the claim in the token. Reject the token if they do not match. -1. Store the token using the browser *sessionStorage* container, or use JavaScript *closures* with *private* variables -1. Add it as a *Bearer* HTTP `Authentication` header with JavaScript when calling services. -1. Add [fingerprint](#token-sidejacking) information to the token. +This means an attacker who steals only the JWT (e.g., via XSS reading `sessionStorage`) cannot use it without also stealing the hardened cookie — which XSS cannot access. Avoid using IP addresses as context; they can change legitimately (mobile networks, VPNs) and raise GDPR concerns. -By storing the token in browser *sessionStorage* container it exposes the token to being stolen through an XSS attack. However, fingerprints added to the token prevent reuse of the stolen token by the attacker on their machine. To close a maximum of exploitation surfaces for an attacker, add a browser [Content Security Policy](https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html) to harden the execution context. +### 7. Lack of Token Revocation -But, we know that *sessionStorage* is not always practical due to its per-tab scope, and the storage method for tokens should balance *security* and *usability*. +**Risk:** JWTs are stateless — they remain valid until they expire even after logout or account compromise, because the server holds no session state to invalidate. -*LocalStorage* is a better method than *sessionStorage* for usability because it allows the session to persist between browser restarts and across tabs, but you must use strict security controls: - -- Tokens stored in *localStorage* should have *short expiration times* (e.g., *15-30 minutes idle timeout, 8-hour absolute timeout*). -- Implement mechanisms such as *token rotation* and *refresh tokens* to minimize risk. - -If *session persistence across tabs* and *sessionStorage* are required, consider using *BroadcastChannel API* or *Single Sign-On (SSO)* to re-authenticate users automatically when they open new tabs. - -An alternative to storing token in browser *sessionStorage* or in *localStorage* is to use JavaScript private variable or Closures. In this, access to all web requests are routed through a JavaScript module that encapsulates the token in a private variable which can not be accessed other than from within the module. - -*Note:* - -- The remaining case is when an attacker uses the user's browsing context as a proxy to use the target application through the legitimate user but the Content Security Policy can prevent communication with non expected domains. -- It's also possible to implement the authentication service in a way that the token is issued within a hardened cookie, but in this case, protection against a [Cross-Site Request Forgery](Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md) attack must be implemented. - -#### Implementation Example - -JavaScript code to store the token after authentication. - -``` javascript -/* Handle request for JWT token and local storage*/ -function authenticate() { - const login = $("#login").val(); - const postData = "login=" + encodeURIComponent(login) + "&password=test"; - - $.post("/services/authenticate", postData, function (data) { - if (data.status == "Authentication successful!") { - ... - sessionStorage.setItem("token", data.token); - } - else { - ... - sessionStorage.removeItem("token"); - } - }) - .fail(function (jqXHR, textStatus, error) { - ... - sessionStorage.removeItem("token"); - }); -} -``` - -JavaScript code to add the token as a *Bearer* HTTP Authentication header when calling a service, for example a service to validate token here. - -``` javascript -/* Handle request for JWT token validation */ -function validateToken() { - var token = sessionStorage.getItem("token"); - - if (token == undefined || token == "") { - $("#infoZone").removeClass(); - $("#infoZone").addClass("alert alert-warning"); - $("#infoZone").text("Obtain a JWT token first :)"); - return; - } - - $.ajax({ - url: "/services/validate", - type: "POST", - beforeSend: function (xhr) { - xhr.setRequestHeader("Authorization", "bearer " + token); - }, - success: function (data) { - ... - }, - error: function (jqXHR, textStatus, error) { - ... - }, - }); -} -``` - -JavaScript code to implement closures with private variables: - -``` javascript -function myFetchModule() { - // Protect the original 'fetch' from getting overwritten via XSS - const fetch = window.fetch; - - const authOrigins = ["https://yourorigin", "http://localhost"]; - let token = ''; - - this.setToken = (value) => { - token = value - } - - this.fetch = (resource, options) => { - let req = new Request(resource, options); - destOrigin = new URL(req.url).origin; - if (token && authOrigins.includes(destOrigin)) { - req.headers.set('Authorization', token); - } - return fetch(req) - } -} - -... - -// usage: -const myFetch = new myFetchModule() - -function login() { - fetch("/api/login") - .then((res) => { - if (res.status == 200) { - return res.json() - } else { - throw Error(res.statusText) - } - }) - .then(data => { - myFetch.setToken(data.token) - console.log("Token received and stored.") - }) - .catch(console.error) -} - -... - -// after login, subsequent api calls: -function makeRequest() { - myFetch.fetch("/api/hello", {headers: {"MyHeader": "foobar"}}) - .then((res) => { - if (res.status == 200) { - return res.text() - } else { - throw Error(res.statusText) - } - }).then(responseText => console.log("helloResponse", responseText)) - .catch(console.error) -} -``` +**Mitigations:** -### Weak Token Secret +- Use **short expiration times** for access tokens (e.g., 15 minutes) to limit the window of exposure. +- Implement a **token denylist** (blocklist): on logout or compromise, store a hash (e.g., SHA-256) of the token `jti` claim (JWT ID) in a fast data store (Redis, database) with a TTL equal to the token's remaining lifetime. Reject any token whose hash is in the denylist. +- Pair access tokens with **refresh tokens** — refresh tokens can be revoked server-side and used to issue new short-lived access tokens. +- Use the Token Fingerprinting approach described in [Token Sidejacking](#6-token-sidejacking) — clearing the fingerprint cookie on logout effectively invalidates the token without server-side state. -#### Symptom +### 8. Header Injection (`kid`, `jku`, `x5u`) -When the token is protected using an HMAC based algorithm, the security of the token is entirely dependent on the strength of the secret used with the HMAC. If an attacker can obtain a valid JWT, they can then carry out an offline attack and attempt to crack the secret using tools such as [John the Ripper](https://github.com/magnumripper/JohnTheRipper) or [Hashcat](https://github.com/hashcat/hashcat). +**Risk:** Certain JWT header parameters can be abused to redirect signature verification to an attacker-controlled key or endpoint: -If they are successful, they would then be able to modify the token and re-sign it with the key they had obtained. This could let them escalate their privileges, compromise other users' accounts, or perform other actions depending on the contents of the JWT. +- **`kid` (Key ID) injection** — If the `kid` value is used unsanitized in a database query or filesystem path, an attacker can craft a `kid` to exploit SQL injection or path traversal, causing the server to verify the signature with an attacker-chosen key. +- **`jku` / `x5u` (JWK Set URL / X.509 URL) injection** — If the library fetches the verification key from a URL specified in the header, an attacker can point it to a server they control. -There are a number of [guides](https://www.notsosecure.com/crafting-way-json-web-tokens/) that document this process in greater detail. +**Mitigations:** -#### How to Prevent +- **Never trust header parameters** for key selection without strict validation. +- Use a **static allowlist** of valid key IDs and ignore any `kid` value not on it. +- **Disable or ignore** `jku` and `x5u` unless your use case explicitly requires them; if used, enforce a strict allowlist of trusted URLs. +- Sanitize all `kid` values before using them in queries or file lookups. -The simplest way to prevent this attack is to ensure that the secret used to sign the JWTs is strong and unique, in order to make it harder for an attacker to crack. As this secret would never need to be typed by a human, it should be at least 64 characters, and generated using a [secure source of randomness](Cryptographic_Storage_Cheat_Sheet.md#secure-random-number-generation). +## Best Practices Summary -Alternatively, consider the use of tokens that are signed with RSA rather than using an HMAC and secret key. +- **Use a reputable, actively-maintained JWT library** for your language/framework. Check [jwt.io/libraries](https://jwt.io/libraries) and the library's CVE history. +- **Prefer asymmetric algorithms** (RS256, ES256, PS256) over symmetric (HS256) when tokens are validated by services other than the one that issued them. +- **Set short expiration times** on access tokens and use refresh tokens for session continuity. +- **Validate all relevant claims** (`exp`, `nbf`, `iss`, `aud`, `sub`) on every request. +- **Never put sensitive data** (passwords, PII, internal secrets) in the payload. +- **Transmit tokens only over HTTPS/TLS**. +- **Rotate signing keys** regularly and provide a JWKS endpoint for public-key distribution. +- **Include a `jti` claim** (unique token ID) to support revocation and replay detection. -#### Further Reading +## References -- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) - A project documents the known attacks and potential security vulnerabilities and misconfigurations of JSON Web Tokens. -- [JWT Best Practices Internet Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-jwt-bcp/) +- [RFC 7519 — JSON Web Token](https://datatracker.ietf.org/doc/html/rfc7519) +- [RFC 7515 — JSON Web Signature](https://datatracker.ietf.org/doc/html/rfc7515) +- [RFC 7516 — JSON Web Encryption](https://datatracker.ietf.org/doc/html/rfc7516) +- [RFC 8725 — JWT Best Current Practices](https://datatracker.ietf.org/doc/html/rfc8725) +- [OWASP Top 10 — Broken Authentication](https://owasp.org/www-project-top-ten/) +- [PortSwigger Web Academy — JWT Attacks](https://portswigger.net/web-security/jwt) +- [{JWT}.{Attack}.Playbook](https://github.com/ticarpi/jwt_tool/wiki) — Documents known JWT attacks and misconfigurations +- [Critical vulnerabilities in JSON Web Token libraries (Auth0 blog)](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/) +- [jwt.io — JWT Debugger and Library Directory](https://jwt.io/)