JWT Signature Verification Bypass
ID |
swift.jwt_signature_verification_bypass |
Severity |
high |
Resource |
Cryptography |
Language |
Swift |
Tags |
CWE:347, NIST.SP.800-53, OWASP:2021:A03, OWASP:2021:A07, PCI-DSS:6.5.10, PCI-DSS:6.5.6, PCI-DSS:6.5.8 |
Description
Improper verification of JWT cryptographic signature.
In Swift applications, JWT signature verification bypass commonly occurs when:
-
SwiftJWT library is used with
verify: falseparameter or.nonealgorithm -
Auth0 JWTDecode.swift library is used for security validation (it never verifies signatures)
-
Vapor JWT is configured with
.nonealgorithm or usesJWTParser.parse()without verification -
JWT tokens are decoded without subsequent signature verification
Rationale
JWT signature verification bypass refers to a scenario where a JSON Web Token, designed to be a secure way to transmit information between parties, is not properly checked for a valid signature.
This can allow attackers to forge tokens, gaining unauthorized access to protected resources or services.
Swift has several popular JWT libraries, each with specific security considerations:
-
SwiftJWT (IBM/Kitura) - Unsafe Patterns
The SwiftJWT library provides multiple ways to decode JWTs, some of which bypass signature verification:
import SwiftJWT
struct MyClaims: Claims {
let sub: String
let admin: Bool
}
// VULNERABLE: decode without verification
func validateTokenUnsafe1(_ tokenString: String) throws -> MyClaims {
// FLAW: Decodes without verifying signature
let jwt = try JWT<MyClaims>.decode(tokenString)
return jwt.claims
}
// VULNERABLE: explicitly disable verification
func validateTokenUnsafe2(_ tokenString: String) throws -> MyClaims {
let key = "secret".data(using: .utf8)!
// FLAW: verify parameter set to false
let jwt = try JWT<MyClaims>.decode(
tokenString,
algorithm: .hs256(key),
verify: false
)
return jwt.claims
}
// VULNERABLE: using .none algorithm
func signTokenUnsafe(_ claims: MyClaims) throws -> String {
var jwt = JWT(claims: claims)
// FLAW: JWTSigner with .none algorithm doesn't sign
let signer = JWTSigner.none()
return try jwt.sign(using: signer)
}
An attacker can:
- Forge tokens without a valid signature
- Modify claims (e.g., change admin: false to admin: true)
- Bypass authentication entirely
-
JWTDecode.swift (Auth0) - CRITICAL MISUSE
The Auth0 JWTDecode.swift library is designed only for reading claims from already-trusted tokens. It NEVER verifies signatures:
import JWTDecode
// VULNERABLE: Using JWTDecode for security validation
func validateTokenWithAuth0(_ tokenString: String) throws -> Bool {
let jwt = try decode(jwt: tokenString)
// FLAW: JWTDecode NEVER verifies signatures
// This check is meaningless without signature verification
guard let exp = jwt.expiresAt, exp > Date() else {
return false
}
// Attacker can forge tokens with any expiration date
return true
}
// VULNERABLE: Checking claims without verification
func isAdmin(_ tokenString: String) -> Bool {
guard let jwt = try? decode(jwt: tokenString) else {
return false
}
// FLAW: Reading admin claim without signature verification
// Attacker can create token with admin=true
return jwt.claim(name: "admin").boolean ?? false
}
CRITICAL: JWTDecode.swift should ONLY be used for: - Reading claims from tokens you already verified (e.g., via SwiftJWT) - Debugging and development - NEVER for security validation or authentication
-
Vapor JWT - Unsafe Patterns
Vapor’s JWT package can be misconfigured to bypass verification:
import JWT
import Vapor
struct UserPayload: JWTPayload {
let sub: String
let exp: ExpirationClaim
let admin: Bool
func verify(using signer: JWTSigner) throws {
try exp.verifyNotExpired()
}
}
// VULNERABLE: Using .none algorithm
func configureJWTUnsafe(app: Application) {
// FLAW: Configuring with .none algorithm disables verification
app.jwt.signers.use(.none())
}
// VULNERABLE: Using JWTParser without verification
func decodeWithoutVerification(_ tokenString: String) throws {
let bytes = Array(tokenString.utf8)
// FLAW: JWTParser.parse() does NOT verify signatures
let (header, payload, signature) = try JWTParser.parse(bytes)
// Processing unverified JWT data
processPayload(payload)
}
// VULNERABLE: Skipping signature check
func validateUnsafe(req: Request) throws -> UserPayload {
// FLAW: If JWT middleware uses .none, this doesn't verify
return try req.jwt.verify(as: UserPayload.self)
}
Remediation
To remediate the JWT signature verification bypass, ensure that you are properly configuring the JWT parser in use, and always verifying the token signature with a trusted public key or secret.
Furthermore, make sure your JWT libraries are updated to the latest versions, which often address security vulnerabilities and provide enhanced capabilities. Additionally, it’s important to apply similar best practices across all environments where JWTs are used or processed to maintain consistent security assurances.
-
Option 1: SwiftJWT with Proper Verification
import SwiftJWT
struct MyClaims: Claims {
let sub: String
let iss: String
let exp: Date
let admin: Bool
}
// FIXED: Use JWTVerifier for secure validation
func validateTokenSecure(_ tokenString: String) throws -> MyClaims {
let secret = getSecretFromEnvironment() // Not hardcoded
// Create verifier with proper algorithm
let verifier = JWTVerifier.hs256(key: secret)
// FIXED: decode with verifier - signatures are verified
let jwt = try JWT<MyClaims>.decode(tokenString, verifier: verifier)
// Additional claim validation
guard jwt.claims.iss == "https://trusted-issuer.com" else {
throw JWTError.invalidIssuer
}
guard jwt.claims.exp > Date() else {
throw JWTError.expired
}
return jwt.claims
}
// FIXED: Proper signing
func signTokenSecure(_ claims: MyClaims) throws -> String {
var jwt = JWT(claims: claims)
let secret = getSecretFromEnvironment()
// FIXED: Use proper algorithm (HS256, RS256, etc.)
let signer = JWTSigner.hs256(key: secret)
return try jwt.sign(using: signer)
}
-
Option 2: Vapor JWT with Secure Configuration
import JWT
import Vapor
struct UserPayload: JWTPayload {
let sub: String
let exp: ExpirationClaim
let admin: Bool
func verify(using signer: JWTSigner) throws {
// Verify expiration
try exp.verifyNotExpired()
// Additional claim validation can go here
}
}
// FIXED: Configure with proper algorithm
func configureJWTSecure(app: Application) throws {
let secret = Environment.get("JWT_SECRET")!
// FIXED: Use secure algorithm (HS256, RS256, etc.)
app.jwt.signers.use(.hs256(key: secret))
}
// FIXED: Use JWTSigners.verify() for validation
func validateSecure(req: Request, token: String) throws -> UserPayload {
// FIXED: This verifies the signature
return try req.application.jwt.signers.verify(token, as: UserPayload.self)
}
// FIXED: Verify in middleware
func jwtMiddleware(req: Request, next: Responder) -> EventLoopFuture<Response> {
guard let token = req.headers.bearerAuthorization?.token else {
return req.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
do {
// FIXED: Verify signature before proceeding
let payload = try req.application.jwt.signers.verify(token, as: UserPayload.self)
req.auth.login(payload)
return next.respond(to: req)
} catch {
return req.eventLoop.makeFailedFuture(Abort(.unauthorized))
}
}
-
Option 3: Never Use JWTDecode for Validation
import JWTDecode
import SwiftJWT
// WRONG: Using JWTDecode for validation
func validateWrong(_ tokenString: String) -> Bool {
guard let jwt = try? decode(jwt: tokenString) else {
return false
}
return jwt.expired == false // WRONG: No signature verification!
}
// CORRECT: Use JWTDecode only after verification
func validateCorrect(_ tokenString: String) throws -> [String: Any] {
let secret = getSecretFromEnvironment()
let verifier = JWTVerifier.hs256(key: secret)
// STEP 1: Verify with SwiftJWT
_ = try JWT<ClaimSet>.decode(tokenString, verifier: verifier)
// STEP 2: Now safe to read claims with JWTDecode (optional)
let jwt = try decode(jwt: tokenString)
return jwt.body
}
Which JWT Library Should I Use?
For Production Security: - ✅ SwiftJWT - Full-featured, supports verification - ✅ Vapor JWT - Well-integrated with Vapor framework - ❌ JWTDecode.swift - NEVER for security validation
Algorithm Recommendations: - ✅ HS256/HS384/HS512 - HMAC with secure secrets - ✅ RS256/RS384/RS512 - RSA with public/private keys - ✅ ES256/ES384/ES512 - ECDSA with public/private keys - ❌ none - NEVER use in production
Key Security Practices
-
Always verify signatures: Use
JWTVerifierorJWTSigners.verify() -
Never use .none algorithm: Always specify a secure algorithm
-
Don’t use JWTDecode for validation: Only for reading already-verified tokens
-
Validate claims: Check issuer, expiration, audience, etc.
-
Use environment variables for secrets: Never hardcode keys
-
Keep libraries updated: Security fixes are released regularly
-
Verify algorithm in header: Prevent algorithm substitution attacks
-
Use HTTPS: Protect tokens in transit
-
Short expiration times: Limit token validity window
-
Implement token revocation: Handle compromised tokens
iOS/macOS Specific Considerations
Keychain Storage:
- Store JWT secrets in Keychain, not UserDefaults
- Use kSecAttrAccessibleWhenUnlocked or stricter
App Transport Security: - Ensure token endpoints use HTTPS - Validate server certificates
Background Processing: - Re-verify tokens when app returns from background - Check expiration on app launch
Configuration
This is a regular (non-tainting) detector that inspects JWT API calls for signature verification bypass patterns.
References
-
CWE-347 : Improper Verification of Cryptographic Signature
-
JWTDecode.swift - WARNING: Does not verify signatures
-
CWE-347: Improper Verification of Cryptographic Signature