Skip to content

Commit

Permalink
Use ErrorType and throws for decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
kylef committed Sep 18, 2015
1 parent 924e5ba commit d303591
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 70 deletions.
23 changes: 7 additions & 16 deletions JWT/Decode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation


/// Failure reasons from decoding a JWT
public enum InvalidToken : CustomStringConvertible {
public enum InvalidToken : CustomStringConvertible, ErrorType {
/// Decoding the JWT itself failed
case DecodeError(String)

Expand Down Expand Up @@ -46,34 +46,25 @@ public enum InvalidToken : CustomStringConvertible {
}


/// Result from decoding a JWT
public enum DecodeResult {
/// Decoding succeeded
case Success(Payload)

/// Decoding failed, take a look at the invalid token reason
case Failure(InvalidToken)
}

/// Decode a JWT
public func decode(jwt:String, algorithms:[Algorithm], verify:Bool = true, audience:String? = nil, issuer:String? = nil) -> DecodeResult {
public func decode(jwt:String, algorithms:[Algorithm], verify:Bool = true, audience:String? = nil, issuer:String? = nil) throws -> Payload {
switch load(jwt) {
case let .Success(header, payload, signature, signatureInput):
if verify {
if let failure = validateClaims(payload, audience: audience, issuer: issuer) ?? verifySignature(algorithms, header: header, signingInput: signatureInput, signature: signature) {
return .Failure(failure)
throw failure
}
}

return .Success(payload)
return payload
case .Failure(let failure):
return .Failure(failure)
throw failure
}
}

/// Decode a JWT
public func decode(jwt:String, algorithm:Algorithm, verify:Bool = true, audience:String? = nil, issuer:String? = nil) -> DecodeResult {
return decode(jwt, algorithms: [algorithm], verify: verify, audience: audience, issuer: issuer)
public func decode(jwt:String, algorithm:Algorithm, verify:Bool = true, audience:String? = nil, issuer:String? = nil) throws -> Payload {
return try decode(jwt, algorithms: [algorithm], verify: verify, audience: audience, issuer: issuer)
}

// MARK: Parsing a JWT
Expand Down
88 changes: 42 additions & 46 deletions JWTTests/JWTTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class JWTEncodeTests : XCTestCase {
builder.issuer = "fuller.li"
}

assertSuccess(JWT.decode(jwt, algorithm: algorithm)) { payload in
assertSuccess(try JWT.decode(jwt, algorithm: algorithm)) { payload in
XCTAssertEqual(payload as NSDictionary, ["iss": "fuller.li"])
}
}
Expand Down Expand Up @@ -76,58 +76,58 @@ class JWTPayloadBuilder : XCTestCase {
class JWTDecodeTests : XCTestCase {
func testDecodingValidJWT() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg"
let result = JWT.decode(jwt, algorithm: .HS256("secret"))
assertSuccess(result) { payload in

assertSuccess(try JWT.decode(jwt, algorithm: .HS256("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["name": "Kyle"])
}
}

func testFailsToDecodeInvalidStringWithoutThreeSegments() {
assertDecodeError(decode("a.b", algorithm: .None), error: "Not enough segments")
assertDecodeError(try decode("a.b", algorithm: .None), error: "Not enough segments")
}

// MARK: Disable verify

func testDisablingVerify() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
assertSuccess(decode(jwt, algorithm: .None, verify:false, issuer:"fuller.li"))
assertSuccess(try decode(jwt, algorithm: .None, verify:false, issuer:"fuller.li"))
}

// MARK: Issuer claim

func testSuccessfulIssuerValidation() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.d7B7PAQcz1E6oNhrlxmHxHXHgg39_k7X7wWeahl8kSQ"
assertSuccess(decode(jwt, algorithm: .HS256("secret"), issuer:"fuller.li")) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"), issuer:"fuller.li")) { payload in
XCTAssertEqual(payload as NSDictionary, ["iss": "fuller.li"])
}
}

func testIncorrectIssuerValidation() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJmdWxsZXIubGkifQ.wOhJ9_6lx-3JGJPmJmtFCDI3kt7uMAMmhHIslti7ryI"
assertFailure(decode(jwt, algorithm: .HS256("secret"), issuer:"querykit.org"))
assertFailure(try decode(jwt, algorithm: .HS256("secret"), issuer:"querykit.org"))
}

func testMissingIssuerValidation() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
assertFailure(decode(jwt, algorithm: .HS256("secret"), issuer:"fuller.li"))
assertFailure(try decode(jwt, algorithm: .HS256("secret"), issuer:"fuller.li"))
}

// MARK: Expiration claim

func testExpiredClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0MjgxODg0OTF9.cy6b2szsNkKnHFnz2GjTatGjoHBTs8vBKnPGZgpp91I"
assertFailure(decode(jwt, algorithm: .HS256("secret")))
assertFailure(try decode(jwt, algorithm: .HS256("secret")))
}

func testInvalidExpiaryClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOlsiMTQyODE4ODQ5MSJdfQ.OwF-wd3THjxrEGUhh6IdnNhxQZ7ydwJ3Z6J_dfl9MBs"
assertFailure(decode(jwt, algorithm: .HS256("secret")))
assertFailure(try decode(jwt, algorithm: .HS256("secret")))
}

func testUnexpiredClaim() {
// If this just started failing, hello 2024!
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjgxODg0OTF9.EW7k-8Mvnv0GpvOKJalFRLoCB3a3xGG3i7hAZZXNAz0"
assertSuccess(decode(jwt, algorithm: .HS256("secret"))) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["exp": 1728188491])
}
}
Expand All @@ -136,131 +136,127 @@ class JWTDecodeTests : XCTestCase {

func testNotBeforeClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE0MjgxODk3MjB9.jFT0nXAJvEwyG6R7CMJlzNJb7FtZGv30QRZpYam5cvs"
assertSuccess(decode(jwt, algorithm: .HS256("secret"))) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["nbf": 1428189720])
}
}

func testInvalidNotBeforeClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOlsxNDI4MTg5NzIwXX0.PUL1FQubzzJa4MNXe2D3d5t5cMaqFr3kYlzRUzly-C8"
assertDecodeError(decode(jwt, algorithm: .HS256("secret")), error: "Not before claim (nbf) must be an integer")
assertDecodeError(try decode(jwt, algorithm: .HS256("secret")), error: "Not before claim (nbf) must be an integer")
}

func testUnmetNotBeforeClaim() {
// If this just started failing, hello 2024!
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MjgxODg0OTF9.Tzhu1tu-7BXcF5YEIFFE1Vmg4tEybUnaz58FR4PcblQ"
assertFailure(decode(jwt, algorithm: .HS256("secret")))
assertFailure(try decode(jwt, algorithm: .HS256("secret")))
}

// MARK: Issued at claim

func testIssuedAtClaimInThePast() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0MjgxODk3MjB9.I_5qjRcCUZVQdABLwG82CSuu2relSdIyJOyvXWUAJh4"
assertSuccess(decode(jwt, algorithm: .HS256("secret"))) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["iat": 1428189720])
}
}

func testIssuedAtClaimInTheFuture() {
// If this just started failing, hello 2024!
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjgxODg0OTF9.owHiJyJmTcW1lBW5y_Rz3iBfSbcNiXlbZ2fY9qR7-aU"
assertFailure(decode(jwt, algorithm: .HS256("secret")))
assertFailure(try decode(jwt, algorithm: .HS256("secret")))
}

func testInvalidIssuedAtClaim() {
// If this just started failing, hello 2024!
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOlsxNzI4MTg4NDkxXX0.ND7QMWtLkXDXH38OaXM3SQgLo3Z5TNgF_pcfWHV_alQ"
assertDecodeError(decode(jwt, algorithm: .HS256("secret")), error: "Issued at claim (iat) must be an integer")
assertDecodeError(try decode(jwt, algorithm: .HS256("secret")), error: "Issued at claim (iat) must be an integer")
}

// MARK: Audience claims

func testAudiencesClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsibWF4aW5lIiwia2F0aWUiXX0.-PKvdNLCClrWG7CvesHP6PB0-vxu-_IZcsYhJxBy5JM"
assertSuccess(decode(jwt, algorithm: .HS256("secret"), audience:"maxine")) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"), audience:"maxine")) { payload in
XCTAssertEqual(payload as NSDictionary, ["aud": ["maxine", "katie"]])
}
}

func testAudienceClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.dpgH4JOwueReaBoanLSxsGTc7AjKUvo7_M1sAfy_xVE"
assertSuccess(decode(jwt, algorithm: .HS256("secret"), audience:"kyle")) { payload in
assertSuccess(try decode(jwt, algorithm: .HS256("secret"), audience:"kyle")) { payload in
XCTAssertEqual(payload as NSDictionary, ["aud": "kyle"])
}
}

func testMismatchAudienceClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJreWxlIn0.VEB_n06pTSLlTXPFkc46ARADJ9HXNUBUPo3VhL9RDe4" // kyle
assertFailure(decode(jwt, algorithm: .HS256("secret"), audience:"maxine"))
assertFailure(try decode(jwt, algorithm: .HS256("secret"), audience:"maxine"))
}

func testMissingAudienceClaim() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w"
assertFailure(decode(jwt, algorithm: .HS256("secret"), audience:"kyle"))
assertFailure(try decode(jwt, algorithm: .HS256("secret"), audience:"kyle"))
}

// MARK: Signature verification

func testNoneAlgorithm() {
let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0."
assertSuccess(decode(jwt, algorithm:.None)) { payload in
assertSuccess(try decode(jwt, algorithm:.None)) { payload in
XCTAssertEqual(payload as NSDictionary, ["test": "ing"])
}
}

func testNoneFailsWithSecretAlgorithm() {
let jwt = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ0ZXN0IjoiaW5nIn0."
assertFailure(decode(jwt, algorithm: .HS256("secret")))
assertFailure(try decode(jwt, algorithm: .HS256("secret")))
}

func testMatchesAnyAlgorithm() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w."
assertFailure(decode(jwt, algorithms: [.HS256("anothersecret"), .HS256("secret")]))
assertFailure(try decode(jwt, algorithms: [.HS256("anothersecret"), .HS256("secret")]))
}

func testHS384Algorithm() {
let jwt = "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.lddiriKLoo42qXduMhCTKZ5Lo3njXxOC92uXyvbLyYKzbq4CVVQOb3MpDwnI19u4"
assertSuccess(decode(jwt, algorithm: .HS384("secret"))) { payload in
assertSuccess(try decode(jwt, algorithm: .HS384("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["some": "payload"])
}
}

func testHS512Algorithm() {
let jwt = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA"
assertSuccess(decode(jwt, algorithm: .HS512("secret"))) { payload in
assertSuccess(try decode(jwt, algorithm: .HS512("secret"))) { payload in
XCTAssertEqual(payload as NSDictionary, ["some": "payload"])
}
}
}

// MARK: Helpers

func assertSuccess(result:DecodeResult, closure:(Payload -> ())? = nil) {
switch result {
case .Success(let payload):
if let closure = closure {
closure(payload)
}
case .Failure(let failure):
XCTFail("Failed to decode while expecting success. \(failure)")
break
func assertSuccess(@autoclosure decoder:() throws -> Payload, closure:(Payload -> ())? = nil) {
do {
let payload = try decoder()
closure?(payload)
} catch {
XCTFail("Failed to decode while expecting success. \(error)")
}
}

func assertFailure(result:DecodeResult, closure:(InvalidToken -> ())? = nil) {
switch result {
case .Success:
XCTFail("Decoded when expecting a failure.")
case .Failure(let failure):
if let closure = closure {
closure(failure)
}
break
func assertFailure(@autoclosure decoder:() throws -> Payload, closure:(InvalidToken -> ())? = nil) {
do {
_ = try decoder()
XCTFail("Decoding succeeded, expected a failure.")
} catch let error as InvalidToken {
closure?(error)
} catch {
XCTFail("Unexpected error")
}
}

func assertDecodeError(result:DecodeResult, error:String) {
assertFailure(result) { failure in
func assertDecodeError(@autoclosure decoder:() throws -> Payload, error:String) {
assertFailure(try decoder()) { failure in
switch failure {
case .DecodeError(let decodeError):
if decodeError != error {
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,18 @@ JWT.encode(.HS256("secret")) { builder in
When decoding a JWT, you must supply one or more algorithms and keys.

```swift
let result = JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w", algorithm: .HS256("secret"))

switch result {
case .Success(let payload):
print(payload)
case .Failure(let failure):
print("decoding failed \(failure)")
do {
let payload = try JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w", algorithm: .HS256("secret"))
print(payload)
} catch {
print("Failed to decode JWT: \(error)")
}
```

When the JWT may be signed with one out of many algorithms or keys:

```swift
let result = JWT.decode("eyJh...5w", algorithms: [.HS256("secret"), .HS256("secret2"), .HS512("secure")])
try JWT.decode("eyJh...5w", algorithms: [.HS256("secret"), .HS256("secret2"), .HS512("secure")])
```

#### Supported claims
Expand Down

0 comments on commit d303591

Please sign in to comment.