Skip to content

Commit

Permalink
Merge pull request #4 from BugsBunnyBR/adds-jwt-provider
Browse files Browse the repository at this point in the history
Add JWT provider
  • Loading branch information
anthonycastelli authored Jun 2, 2017
2 parents 0646e83 + c68def6 commit 91b7763
Show file tree
Hide file tree
Showing 10 changed files with 140 additions and 20 deletions.
7 changes: 7 additions & 0 deletions Config/jwt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"signer": {
"type": "rsa",
"key": "MIIEowIBAAKCAQEA2MBhga9j06SCZcYr9JLfQg3TnUZnJfN6fl5c/nAoTx8kv/iVP2EtxQt89KcXyKNLnl2z5HQfQgAGnioPkDHcqd2XVyNxxXa2DAtfL34EeNnl/4gV6WXPw6hZ8s2X+OkTTTStqdxfgtxXCKbRRcOBTJa/F3eY9jBW16jujygkyt+WwD28XcQSs4enLhHOyljslhLv6ebqJ3cNI7+j3St4JYmB+SAAK2di1tzU2OaMlcUo7bEtyqzDiZKsXBtLKTBVQ9TbwFiFQ+9IfkegOMEC7P02N9iQ/11urMU/rTrb6fKB6/G7KfFCQhWj+QKZGewXC+PYOMYJgUMk1WRsDcQScwIDAQABAoIBAF1Hja7t+Bwg9C0wd8ItYv9eS++nWMSwX8r6eTLWucIzOPGU3UYFYFkodIIlVsr125kv4jcy8jDJKg/vMftwOfKwdmz9x/ye9gGA81nQ9cO8ooqx2hwzwJIHZY5khD6Or8vOG966BDCg+qOyhuVrGb4IMfy7b4yjiPwOq3vYXt0fSU/zA8U6akCFrIAFICH1rmWqwSsz9CtN5Bz261+seqEtEFLRDtdSxzrYEn1GnWni8DayOaB35fHdDuVRsaxKtSfQ7yIOrPXlRkqTe/sNz891TlwS0G6NHas6E+ZPQljtGBef/AIrtTEHmUdc5wXS0Mmgu+yvzSgVRntNhfX4QiECgYEA7ugr2NDDRgg7el0CspJAzPWH/JotoVb715lZJJ8tbu7NTQfL6bnUzQj6kF78DXOyXYp11lTiEpRzLOKXUKy1VdJ5Du0XkKC/ogR4/5QEdk7RZ1In9DwdE2AXX57hUri3ObMsVpF0+nZWmF1wRs+JYUh2fTBeo1ZWPA/DvHFGTTcCgYEA6EJo60gug7z6zVBorZBgTGZCfg8DwW7CvTGQ785sK5TYcKHgXM2PQhDXEqai1rNoIgmRYtYHDDeS39kR6UuJCRPZbnkDwEcXbpCE5rqmTa+7yO4s0E5di57z9Jos31jPqMFbYU73cHPy8XnlMD8KiHwR/krGgacukK+pdfXlIqUCgYBnlVSFgiZYc/NN34vu3sin1QEr/bExFeTFmuByp21sfq+W6X15DjB84Zq6A+Tm9DXuprzmvBD1G1ZArNIMkYVh+4qvdQ7Vj0znM2c+8O9qWEwkrxNRqsq0fuJDfECXvCz9IHll41VDzxFGqKSonw0il+d/6fvud92V1wP37WkcywKBgQChfKM0jBCDWl9LZ9AQdaTvGd67hTcIRDm0kAUFJ5JATxKaZYL5I5eqyMixWBk6jK0nlV13yfZGgVFmwKfafMGABUQVsqBwDT32ixdM0ZQVyc0YLLoN757NGCzo8lWmyTpBTId7xgr3LjdJvIYlIH/zW8iq9VTGCvaudOSvdtPlXQKBgHmHsipMpFlZeJDtFnlyAGJTte/lowVN9rHm8q8gYioWgLaCxY2bqQRlBzCiVnvhBfKY04QbvwGMBriFhQisaV/0tdr7NgTVSPbFog3+LmlZ7EGGoYgXqmyHAPibHAScbdHrjOGP3lPasJmKqtVgN11dtJoNRj8GkQle2s1Hljnf",
"algorithm": "rs256"
}
}
10 changes: 8 additions & 2 deletions Package.pins
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
"repositoryURL": "https://github.com/vapor/jwt.git",
"version": "2.1.0"
},
{
"package": "JWTProvider",
"reason": null,
"repositoryURL": "https://github.com/vapor/jwt-provider.git",
"version": "1.0.0"
},
{
"package": "Multipart",
"reason": null,
Expand Down Expand Up @@ -125,13 +131,13 @@
"package": "TLS",
"reason": null,
"repositoryURL": "https://github.com/vapor/tls.git",
"version": "2.0.1"
"version": "2.0.3"
},
{
"package": "Vapor",
"reason": null,
"repositoryURL": "https://github.com/vapor/vapor.git",
"version": "2.0.4"
"version": "2.0.5"
}
],
"version": 1
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
.Package(url: "https://github.com/vapor/vapor.git", majorVersion: 2),
.Package(url: "https://github.com/vapor/fluent-provider.git", majorVersion: 1),
.Package(url: "https://github.com/vapor/auth-provider.git", majorVersion: 1),
.Package(url: "https://github.com/vapor/jwt.git", majorVersion: 2)
.Package(url: "https://github.com/vapor/jwt-provider.git", majorVersion: 1)
],
exclude: [
"Config",
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@

## Generate JWT key
```
ssh-keygen -t rsa -b 2048 -f jwtRS256.key
# Don't add passphrase
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
cat jwtRS256.key
```
It will print something like this
```
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA2MBhga9j06SCZcYr9JLfQg3TnUZnJfN6fl5c/nAoTx8kv/iV
P2EtxQt89KcXyKNLnl2z5HQfQgAGnioPkDHcqd2XVyNxxXa2DAtfL34EeNnl/4gV
6WXPw6hZ8s2X+OkTTTStqdxfgtxXCKbRRcOBTJa/F3eY9jBW16jujygkyt+WwD28
XcQSs4enLhHOyljslhLv6ebqJ3cNI7+j3St4JYmB+SAAK2di1tzU2OaMlcUo7bEt
yqzDiZKsXBtLKTBVQ9TbwFiFQ+9IfkegOMEC7P02N9iQ/11urMU/rTrb6fKB6/G7
KfFCQhWj+QKZGewXC+PYOMYJgUMk1WRsDcQScwIDAQABAoIBAF1Hja7t+Bwg9C0w
d8ItYv9eS++nWMSwX8r6eTLWucIzOPGU3UYFYFkodIIlVsr125kv4jcy8jDJKg/v
MftwOfKwdmz9x/ye9gGA81nQ9cO8ooqx2hwzwJIHZY5khD6Or8vOG966BDCg+qOy
huVrGb4IMfy7b4yjiPwOq3vYXt0fSU/zA8U6akCFrIAFICH1rmWqwSsz9CtN5Bz2
61+seqEtEFLRDtdSxzrYEn1GnWni8DayOaB35fHdDuVRsaxKtSfQ7yIOrPXlRkqT
e/sNz891TlwS0G6NHas6E+ZPQljtGBef/AIrtTEHmUdc5wXS0Mmgu+yvzSgVRntN
hfX4QiECgYEA7ugr2NDDRgg7el0CspJAzPWH/JotoVb715lZJJ8tbu7NTQfL6bnU
zQj6kF78DXOyXYp11lTiEpRzLOKXUKy1VdJ5Du0XkKC/ogR4/5QEdk7RZ1In9Dwd
E2AXX57hUri3ObMsVpF0+nZWmF1wRs+JYUh2fTBeo1ZWPA/DvHFGTTcCgYEA6EJo
60gug7z6zVBorZBgTGZCfg8DwW7CvTGQ785sK5TYcKHgXM2PQhDXEqai1rNoIgmR
YtYHDDeS39kR6UuJCRPZbnkDwEcXbpCE5rqmTa+7yO4s0E5di57z9Jos31jPqMFb
YU73cHPy8XnlMD8KiHwR/krGgacukK+pdfXlIqUCgYBnlVSFgiZYc/NN34vu3sin
1QEr/bExFeTFmuByp21sfq+W6X15DjB84Zq6A+Tm9DXuprzmvBD1G1ZArNIMkYVh
+4qvdQ7Vj0znM2c+8O9qWEwkrxNRqsq0fuJDfECXvCz9IHll41VDzxFGqKSonw0i
l+d/6fvud92V1wP37WkcywKBgQChfKM0jBCDWl9LZ9AQdaTvGd67hTcIRDm0kAUF
J5JATxKaZYL5I5eqyMixWBk6jK0nlV13yfZGgVFmwKfafMGABUQVsqBwDT32ixdM
0ZQVyc0YLLoN757NGCzo8lWmyTpBTId7xgr3LjdJvIYlIH/zW8iq9VTGCvaudOSv
dtPlXQKBgHmHsipMpFlZeJDtFnlyAGJTte/lowVN9rHm8q8gYioWgLaCxY2bqQRl
BzCiVnvhBfKY04QbvwGMBriFhQisaV/0tdr7NgTVSPbFog3+LmlZ7EGGoYgXqmyH
APibHAScbdHrjOGP3lPasJmKqtVgN11dtJoNRj8GkQle2s1Hljnf
-----END RSA PRIVATE KEY-----
```
Remove the BEGIN and END delimiters and the line breaks before adding it to jwt.json

<p align="center">
<img src="https://cloud.githubusercontent.com/assets/1342803/24797159/52fb0d88-1b90-11e7-85a5-359fff0496a4.png" width="320" alt="MySQL">
<br>
Expand Down
4 changes: 4 additions & 0 deletions Sources/App/Config+Setup.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import FluentProvider
import AuthProvider
import JWTProvider

extension Config {
public func setup() throws {
Expand All @@ -13,6 +15,8 @@ extension Config {
/// Configure providers
private func setupProviders() throws {
try addProvider(FluentProvider.Provider.self)
try addProvider(AuthProvider.Provider.self)
try addProvider(JWTProvider.Provider.self)
}

/// Add all models that should have their
Expand Down
17 changes: 6 additions & 11 deletions Sources/App/Controllers/UserController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import AuthProvider
import JWT

final class UserController {
let droplet: Droplet
init(_ droplet: Droplet) {
self.droplet = droplet
}

func register(request: Request) throws -> ResponseRepresentable {
// Get our credentials
Expand All @@ -34,7 +38,7 @@ final class UserController {
}

return try JSON(node: [
"access_token": self.generateJWTToken(userId),
"access_token": try droplet.createJwtToken(String(userId)),
"user": user
])
}
Expand All @@ -52,7 +56,7 @@ final class UserController {
}

return try JSON(node: [
"access_token": self.generateJWTToken(userId),
"access_token": try droplet.createJwtToken(String(userId)),
"user": user
])
}
Expand All @@ -67,13 +71,4 @@ final class UserController {
return try request.user()
}

// MARK: JWT Token Generation

func generateJWTToken(_ userId: Int) throws -> String {
let time = ExpirationTimeClaim(date: Date().addingTimeInterval(60 * 5)) // 5 minutes
let payload: [Claim] = [time, SubjectClaim(string: "\(userId)")]
let jwt = try JWT(payload: JSON(payload), signer: HS256(key: "SIGNING_KEY".makeBytes()))
return try jwt.createToken()
}

}
24 changes: 24 additions & 0 deletions Sources/App/Droplet+JWT.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Vapor
import HTTP
import JWT


extension Droplet {
func createJwtToken(_ userId: String) throws -> String {

guard let sig = self.signer else {
throw Abort.unauthorized
}

let timeToLive = 5 * 60.0 // 5 minutes
let claims:[Claim] = [
ExpirationTimeClaim(date: Date().addingTimeInterval(timeToLive)),
SubjectClaim(string: userId)
]

let payload = JSON(claims)
let jwt = try JWT(payload: payload, signer: sig)

return try jwt.createToken()
}
}
2 changes: 1 addition & 1 deletion Sources/App/Droplet+Setup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ extension Droplet {
public func setup() throws {

// Do any additional droplet setup
try collection(GenealRoutes.self)
try collection(GeneralRoutes(self))
}
}
38 changes: 38 additions & 0 deletions Sources/App/Models/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import Vapor
import FluentProvider
import AuthProvider
import JWTProvider
import JWT
import HTTP

Expand Down Expand Up @@ -137,6 +138,43 @@ extension User: TokenAuthenticatable {
}
}


class Claims: JSONInitializable {
var subjectClaimValue : String
var expirationTimeClaimValue : Double
public required init(json: JSON) throws {
guard let subjectClaimValue = try json.get(SubjectClaim.name) as String? else {
throw AuthenticationError.invalidCredentials
}
self.subjectClaimValue = subjectClaimValue

guard let expirationTimeClaimValue = try json.get(ExpirationTimeClaim.name) as String? else {
throw AuthenticationError.invalidCredentials
}
self.expirationTimeClaimValue = Double(expirationTimeClaimValue)!

}
}

extension User: PayloadAuthenticatable {
typealias PayloadType = Claims
static func authenticate(_ payload: Claims) throws -> User {
if payload.expirationTimeClaimValue < Date().timeIntervalSince1970 {
throw AuthenticationError.invalidCredentials
}

let userId = payload.subjectClaimValue
guard let user = try User.makeQuery()
.filter(idKey, userId)
.first()
else {
throw AuthenticationError.invalidCredentials
}

return user
}
}

// MARK: JSON

extension User: JSONConvertible {
Expand Down
16 changes: 11 additions & 5 deletions Sources/App/Routes.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import Vapor
import HTTP
import AuthProvider
import JWTProvider

final class GenealRoutes: RouteCollection {
final class GeneralRoutes: RouteCollection {
var droplet: Droplet
init(_ droplet: Droplet) {
self.droplet = droplet
}
func build(_ builder: RouteBuilder) throws {
let api = builder.grouped("api")
let v1 = api.grouped("v1")

let userController = UserController()
let userController = UserController(self.droplet)
v1.post("register", handler: userController.register)
v1.post("login", handler: userController.login)
v1.post("logout", handler: userController.logout)

let secured = v1.grouped(TokenAuthenticationMiddleware(User.self))
//NOTE: TokenAuthenticationMiddleware should be used only to fluent token auth, not JWT
//let secured = v1.grouped(TokenAuthenticationMiddleware(User.self))
let tokenMiddleware = PayloadAuthenticationMiddleware(self.droplet.signer!,[], User.self)
let secured = v1.grouped(tokenMiddleware)
let users = secured.grouped("users")
users.get("me", handler: userController.me)
}
}

extension GenealRoutes: EmptyInitializable { }

0 comments on commit 91b7763

Please sign in to comment.