Xss Cross Site Scripting in Adonisjs with Jwt Tokens
Xss Cross Site Scripting in Adonisjs with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Cross-site scripting (XSS) in an AdonisJS application that uses JWT tokens typically arises when an application embeds data from an untrusted source into HTML/JavaScript without proper sanitization or escaping, and the data is included in a context where it can be executed. JWT tokens themselves are cryptographically signed and encoded, but they are not immune to XSS when their payload is used in a browser context. If your AdonisJS API issues a JWT and that token’s claims (for example, a user-supplied name or role) are later rendered into a page served to the browser without escaping, an attacker can inject script content that executes in other users’ sessions.
Consider a scenario where an AdonisJS route decodes a JWT on the server, extracts a user-supplied field such as displayName, and passes it to a view template. If the template directly interpolates the value without escaping, an attacker who can influence the JWT payload (for instance, via an insecure sub claim or a custom claim set by a compromised admin) can cause stored or reflected XSS. Another relevant pattern is an admin endpoint that returns user data (including decoded JWT claims) to a frontend framework that then dynamically renders HTML using JavaScript; if the frontend inserts JSON directly into the DOM (e.g., using innerHTML), malicious payloads encoded in the JWT can execute.
Additionally, if your AdonisJS application embeds a JWT into a URL as a query parameter or fragment and that URL is rendered in HTML without proper encoding, an attacker may craft a token containing a script that gets executed when the URL is visited. This often occurs in single-page app flows where the client parses the token from the URL and uses it to personalize the UI. The risk is heightened when developers assume the signature guarantees safety and overlook output encoding in templates and client-side rendering.
In practice, XSS with JWTs in AdonisJS is less about breaking the token validation and more about failing to treat data extracted from the token as untrusted input. Any time a JWT claim is placed into HTML, JavaScript, or CSS without context-aware escaping, you open the door to injection. The token’s integrity does not prevent the browser from executing malicious strings that were intentionally placed there by an attacker who controlled part of the payload or tricked an admin into issuing a token containing harmful data.
To detect this class of issue, scanners check whether responses that include tokens or data derived from tokens reflect user-controlled values into HTML/JS without sanitization. They also look for endpoints that issue tokens with overly permissive claims and UI flows that deserialize tokens in the browser and inject them into the DOM. Remediation is not about changing how the token is signed, but about ensuring that any data from the token is escaped for the target context and that least-privilege claims are used.
Jwt Tokens-Specific Remediation in Adonisjs — concrete code fixes
Remediate XSS when using JWT tokens in AdonisJS by treating all claims as untrusted and applying context-specific escaping before rendering. Below are concrete patterns and code examples to safely handle JWT data in templates and APIs.
1. Server-side rendering with escaped output
When rendering HTML views, escape data extracted from the JWT using the template engine’s escaping utilities. For Edge templates (default in AdonisJS), use {{ }} which auto-escapes by default. Avoid marking content as safe unless you have sanitized it.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import UserValidator from 'App/Validators/UserValidator'
export default class UserController {
public async profile({ auth, view }: HttpContextContract) {
const user = auth.user!
// JWT is typically verified by an auth provider; user is already available
// If you explicitly decode a token, use:
// const token = request.header('authorization')?.replace('Bearer ', '')
// const payload = Jwt.verify(token, 'YOUR_SECRET')
return view.render('profile', {
displayName: user.displayName,
// Edge will escape this by default
})
}
}
In the Edge template (resources/views/profile.edge), ensure you do not use !{ } unless you have sanitized the content:
<div>Welcome, {{ displayName }}</div>
2. API responses: encode for HTML/JS contexts
If your endpoint returns JSON that will be rendered client-side, do not rely on the client to sanitize. Instead, ensure the server does not send executable strings in fields that will be injected as HTML. If you must send rich content, provide it as sanitized HTML or use a strict allowlist.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class UserController {
public async me({ auth }: HttpContextContract) {
const user = auth.user!
// Return plain data; let the client handle rendering safely
return {
id: user.id,
// Keep free-text fields as plain strings; escape on the client if inserting into HTML
displayName: user.displayName,
roles: user.roles.map((r) => r.name)
}
}
}
3. Avoid injecting JWT claims into href/src/style attributes
Do not directly embed JWT claims into URLs or style attributes without encoding. Use libraries to build URLs safely and avoid javascript: handlers.
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class LinkController {
public build({ auth }: HttpContextContract) {
const user = auth.user!
// Safe: use a path builder, not string concatenation with untrusted input
const url = `/dashboard?user=${encodeURIComponent(user.id)}`
return { url }
}
}
4. Validate and limit JWT claims
Issue tokens with minimal claims and validate them server-side. In AdonisJS, configure your auth provider to expect strict payload shapes and reject tokens with unexpected fields.
// Example JWT verification with strict payload checks
import { jwtVerify } from 'jose'
const encoder = new TextEncoder()
const token = 'YOUR_JWT_HERE'
const { payload } = await jwtVerify(token, encoder.encode(process.env.JWT_SECRET!))
if (typeof payload.name !== 'string' || payload.name.length > 128) {
throw new Error('Invalid token claims')
}
5. Client-side handling
If you must parse a JWT in the browser, avoid inserting raw claims into innerHTML. Use textContent or a safe templating library that escapes by default.
// Instead of: element.innerHTML = tokenPayload.name
element.textContent = tokenPayload.name
6. Security headers and CSP
Add Content-Security-Policy headers to mitigate the impact of any accidental injection. In AdonisJS, use middleware to set headers.
export default class SecurityHeadersMiddleware {
public async handle(ctx, next) {
ctx.response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';"
)
await next()
}
}
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |