Skip to content

Commit b30f707

Browse files
committed
Various refactoring
1 parent d871f44 commit b30f707

File tree

5 files changed

+118
-77
lines changed

5 files changed

+118
-77
lines changed

JWT.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
279D63A81AD07FFF0024E2BC /* JWT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 279D639C1AD07FFF0024E2BC /* JWT.framework */; };
1212
279D63AF1AD07FFF0024E2BC /* JWTTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279D63AE1AD07FFF0024E2BC /* JWTTests.swift */; };
1313
279D63B91AD0803F0024E2BC /* JWT.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279D63B81AD0803F0024E2BC /* JWT.swift */; };
14+
279D63BB1AD0E3FA0024E2BC /* Claims.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279D63BA1AD0E3FA0024E2BC /* Claims.swift */; };
1415
885619E9E1C342A9D8BD77B7 /* Pods_JWT.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 540942F3614C41E3827F2013 /* Pods_JWT.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
1516
EBEC5851F5183DF2D7BFE1AF /* Pods_JWTTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE8198B6E30BA6B8F8125FA7 /* Pods_JWTTests.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
1617
/* End PBXBuildFile section */
@@ -33,6 +34,7 @@
3334
279D63AD1AD07FFF0024E2BC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3435
279D63AE1AD07FFF0024E2BC /* JWTTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JWTTests.swift; sourceTree = "<group>"; };
3536
279D63B81AD0803F0024E2BC /* JWT.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JWT.swift; sourceTree = "<group>"; };
37+
279D63BA1AD0E3FA0024E2BC /* Claims.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Claims.swift; sourceTree = "<group>"; };
3638
3BD8D638895FE8AF4FDDA8A9 /* Pods-JWTTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JWTTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-JWTTests/Pods-JWTTests.debug.xcconfig"; sourceTree = "<group>"; };
3739
540942F3614C41E3827F2013 /* Pods_JWT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JWT.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3840
56671E3EAC540766DE31974E /* Pods-JWT.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JWT.release.xcconfig"; path = "Pods/Target Support Files/Pods-JWT/Pods-JWT.release.xcconfig"; sourceTree = "<group>"; };
@@ -89,6 +91,7 @@
8991
children = (
9092
279D63A11AD07FFF0024E2BC /* JWT.h */,
9193
279D63B81AD0803F0024E2BC /* JWT.swift */,
94+
279D63BA1AD0E3FA0024E2BC /* Claims.swift */,
9295
279D639F1AD07FFF0024E2BC /* Supporting Files */,
9396
);
9497
path = JWT;
@@ -345,6 +348,7 @@
345348
isa = PBXSourcesBuildPhase;
346349
buildActionMask = 2147483647;
347350
files = (
351+
279D63BB1AD0E3FA0024E2BC /* Claims.swift in Sources */,
348352
279D63B91AD0803F0024E2BC /* JWT.swift in Sources */,
349353
);
350354
runOnlyForDeploymentPostprocessing = 0;

JWT/Claims.swift

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import Foundation
2+
3+
func validateClaims(payload:Payload, audience:String?, issuer:String?) -> InvalidToken? {
4+
return validateIssuer(payload, issuer) ?? validateAudience(payload, audience) ??
5+
validateDate(payload, "exp", .OrderedAscending, .ExpiredSignature, "Expiration time claim (exp) must be an integer") ??
6+
validateDate(payload, "nbf", .OrderedDescending, .ImmatureSignature, "Not before claim (nbf) must be an integer") ??
7+
validateDate(payload, "iat", .OrderedDescending, .InvalidIssuedAt, "Issued at claim (iat) must be an integer")
8+
}
9+
10+
func validateAudience(payload:Payload, audience:String?) -> InvalidToken? {
11+
if let audience = audience {
12+
if let aud = payload["aud"] as? [String] {
13+
if !contains(aud, audience) {
14+
return .InvalidAudience
15+
}
16+
} else if let aud = payload["aud"] as? String {
17+
if aud != audience {
18+
return .InvalidAudience
19+
}
20+
} else {
21+
return .DecodeError("Invalid audience claim, must be a string or an array of strings")
22+
}
23+
}
24+
25+
return nil
26+
}
27+
28+
func validateIssuer(payload:Payload, issuer:String?) -> InvalidToken? {
29+
if let issuer = issuer {
30+
if let iss = payload["iss"] as? String {
31+
if iss != issuer {
32+
return .InvalidIssuer
33+
}
34+
} else {
35+
return .InvalidIssuer
36+
}
37+
}
38+
39+
return nil
40+
}
41+
42+
func validateDate(payload:Payload, key:String, comparison:NSComparisonResult, failure:InvalidToken, decodeError:String) -> InvalidToken? {
43+
if let timestamp = payload[key] as? NSTimeInterval {
44+
let date = NSDate(timeIntervalSince1970: timestamp)
45+
if date.compare(NSDate()) == comparison {
46+
return failure
47+
}
48+
} else if let timestamp:AnyObject = payload[key] {
49+
return .DecodeError(decodeError)
50+
}
51+
52+
return nil
53+
}

JWT/JWT.swift

Lines changed: 54 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,52 @@ public enum InvalidToken : Printable {
3636
}
3737
}
3838

39+
/// The supported Algorithms
40+
public enum Algorithm : Printable {
41+
/// No Algorithm, i-e, insecure
42+
case None
43+
44+
/// HMAC using SHA-256 hash algorithm
45+
case HS256(String)
46+
47+
static func algorithm(name:String, key:String?) -> Algorithm? {
48+
if name == "none" {
49+
return Algorithm.None
50+
} else if let key = key {
51+
if name == "HS256" {
52+
return .HS256(key)
53+
}
54+
}
55+
56+
return nil
57+
}
58+
59+
public var description:String {
60+
switch self {
61+
case .None:
62+
return "none"
63+
case .HS256(let key):
64+
return "HS256"
65+
}
66+
}
67+
68+
func sign(message:String) -> String {
69+
switch self {
70+
case .None:
71+
return ""
72+
73+
case .HS256(let key):
74+
let mac = Authenticator.HMAC(key: key.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, variant:.sha256)
75+
let result = mac.authenticate(message.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)!
76+
return base64encode(result)
77+
}
78+
}
79+
80+
func verify(message:String, signature:NSData) -> Bool {
81+
return sign(message) == base64encode(signature)
82+
}
83+
}
84+
3985
public enum DecodeResult {
4086
case Success(Payload)
4187
case Failure(InvalidToken)
@@ -60,8 +106,7 @@ public func decode(jwt:String, key:String? = nil, verify:Bool = true, audience:S
60106

61107

62108
/// Encoding a payload
63-
64-
public func encode(payload:Payload, key:String) -> String {
109+
public func encode(payload:Payload, algorithm:Algorithm) -> String {
65110
func encode(payload:Payload) -> String? {
66111
if let data = NSJSONSerialization.dataWithJSONObject(payload, options: NSJSONWritingOptions(0), error: nil) {
67112
return base64encode(data)
@@ -70,13 +115,10 @@ public func encode(payload:Payload, key:String) -> String {
70115
return nil
71116
}
72117

73-
let algorithm = "HS256"
74-
let header = encode(["typ": "JWT", "alg": algorithm])!
118+
let header = encode(["typ": "JWT", "alg": algorithm.description])!
75119
let payload = encode(payload)!
76120
let signingInput = "\(header).\(payload)"
77-
let mac = Authenticator.HMAC(key: key.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!, variant:.sha256)
78-
let result = mac.authenticate(signingInput.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)!
79-
let signature = base64encode(result)
121+
let signature = algorithm.sign(signingInput)
80122
return "\(signingInput).\(signature)"
81123
}
82124

@@ -156,80 +198,16 @@ func load(jwt:String) -> LoadResult {
156198

157199
func verifySignature(header:Payload, signingInput:String, signature:NSData, key:String?) -> InvalidToken? {
158200
if let alg = header["alg"] as? String {
159-
switch alg {
160-
case "none":
201+
if let algoritm = Algorithm.algorithm(alg, key: key) {
202+
if algoritm.verify(signingInput, signature: signature) {
161203
return nil
162-
case "HS256":
163-
if let key = key?.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
164-
let mac = Authenticator.HMAC(key: key, variant:.sha256)
165-
let result = mac.authenticate(signingInput.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
166-
if result! == signature {
167-
return nil
168-
} else {
169-
return .DecodeError("Signature verification failed")
170-
}
171-
}
172-
break
173-
default:
174-
break
204+
} else {
205+
return .DecodeError("Signature verification failed")
206+
}
175207
}
176208

177209
return .InvalidAlgorithm
178210
}
179211

180212
return .DecodeError("Missing Algorithm")
181213
}
182-
183-
// MARK: Claim Validation
184-
185-
func validateAudience(payload:Payload, audience:String?) -> InvalidToken? {
186-
if let audience = audience {
187-
if let aud = payload["aud"] as? [String] {
188-
if !contains(aud, audience) {
189-
return .InvalidAudience
190-
}
191-
} else if let aud = payload["aud"] as? String {
192-
if aud != audience {
193-
return .InvalidAudience
194-
}
195-
} else {
196-
return .DecodeError("Invalid audience claim, must be a string or an array of strings")
197-
}
198-
}
199-
200-
return nil
201-
}
202-
203-
func validateIssuer(payload:Payload, issuer:String?) -> InvalidToken? {
204-
if let issuer = issuer {
205-
if let iss = payload["iss"] as? String {
206-
if iss != issuer {
207-
return .InvalidIssuer
208-
}
209-
} else {
210-
return .InvalidIssuer
211-
}
212-
}
213-
214-
return nil
215-
}
216-
217-
func validateDate(payload:Payload, key:String, comparison:NSComparisonResult, failure:InvalidToken, decodeError:String) -> InvalidToken? {
218-
if let timestamp = payload[key] as? NSTimeInterval {
219-
let date = NSDate(timeIntervalSince1970: timestamp)
220-
if date.compare(NSDate()) == comparison {
221-
return failure
222-
}
223-
} else if let timestamp:AnyObject = payload[key] {
224-
return .DecodeError(decodeError)
225-
}
226-
227-
return nil
228-
}
229-
230-
func validateClaims(payload:Payload, audience:String?, issuer:String?) -> InvalidToken? {
231-
return validateIssuer(payload, issuer) ?? validateAudience(payload, audience) ??
232-
validateDate(payload, "exp", .OrderedAscending, .ExpiredSignature, "Expiration time claim (exp) must be an integer") ??
233-
validateDate(payload, "nbf", .OrderedDescending, .ImmatureSignature, "Not before claim (nbf) must be an integer") ??
234-
validateDate(payload, "iat", .OrderedDescending, .InvalidIssuedAt, "Issued at claim (iat) must be an integer")
235-
}

JWTTests/JWTTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import JWT
44
class JWTEncodeTests : XCTestCase {
55
func testEncodingJWT() {
66
let payload = ["name": "Kyle"] as Payload
7-
let jwt = JWT.encode(payload, "secret")
7+
let jwt = JWT.encode(payload, .HS256("secret"))
88
let fixture = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiS3lsZSJ9.zxm7xcp1eZtZhp4t-nlw09ATQnnFKIiSN83uG8u6cAg"
99
XCTAssertEqual(jwt, fixture)
1010
}

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import JWT
2222
JWT.decode("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.2_8pWJfyPup0YwOXK7g9Dn0cF1E3pdn299t4hSeJy5w")
2323
```
2424

25+
### Encoding a claim
26+
27+
```swift
28+
JWT.encode(["my": "payload"], .HS256("secret"))
29+
```
30+
2531
#### Supported claims
2632

2733
- Issuer (`iss`) Claim

0 commit comments

Comments
 (0)