Skip to content

Commit

Permalink
Add JWTKit post (#22)
Browse files Browse the repository at this point in the history
* Add JWTKit post

* Update Sources/Articles/Documentation.docc/2025/jwt-kit/jwt-kit.md

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>

* Update Sources/Articles/Documentation.docc/2025/jwt-kit/jwt-kit.md

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>

* Update Sources/Articles/Documentation.docc/2025/jwt-kit/jwt-kit.md

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>

* Update JWTKit post

* Update Sources/Articles/Documentation.docc/2025/jwt-kit/jwt-kit.md

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>

* Update JWTKit post

* Update title

* Update snippets

* Update snippets

* Use custom claim

* Update link

* Update title

---------

Co-authored-by: Joannis Orlandos <joannis@orlandos.nl>
  • Loading branch information
ptoffy and Joannis authored Jan 9, 2025
1 parent 896bea5 commit 3c4bc87
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
18 changes: 18 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@
"version" : "5.1.1"
}
},
{
"identity" : "jwt-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/jwt-kit.git",
"state" : {
"revision" : "6f745e91e2422608fe14c9a66ee3826cb661e2a6",
"version" : "5.1.1"
}
},
{
"identity" : "mongokitten",
"kind" : "remoteSourceControl",
Expand Down Expand Up @@ -181,6 +190,15 @@
"version" : "3.10.0"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779",
"version" : "3.10.0"
}
},
{
"identity" : "swift-distributed-tracing",
"kind" : "remoteSourceControl",
Expand Down
36 changes: 23 additions & 13 deletions Snippets/jwt-kit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,31 @@ let verifiedPayload = try await keyCollection.verify(
)
// snippet.end

// snippet.auth_user_role_claim
struct RoleClaim: JWTClaim {
var value: [String]

func verifyAdmin() throws {
guard value.contains("admin") else {
throw JWTError.claimVerificationFailure(
failedClaim: self,
reason: "User is not an admin"
)
}
}
}
// snippet.end

struct User {
var id: Int
var roles: RoleClaim
}

// snippet.auth_user_payload
struct UserPayload: JWTPayload {
let userID: UUID
let userID: Int
let expiration: ExpirationClaim
let roles: [String]
let roles: RoleClaim

enum CodingKeys: String, CodingKey {
case userID = "user_id"
Expand All @@ -55,12 +75,7 @@ struct UserPayload: JWTPayload {

func verify(using key: some JWTAlgorithm) throws {
try expiration.verifyNotExpired()
guard roles.contains("admin") else {
throw JWTError.claimVerificationFailure(
failedClaim: nil,
reason: "User is not an admin"
)
}
try roles.verifyAdmin()
}

init(from user: User) {
Expand All @@ -70,8 +85,3 @@ struct UserPayload: JWTPayload {
}
}
// snippet.end

struct User: Identifiable {
let id: UUID
let roles: [String]
}
26 changes: 16 additions & 10 deletions Sources/Articles/Documentation.docc/2025/jwt-kit/jwt-kit.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# A Practical Introduction to JWTs in Swift using JWTKit
# Introduction to JWTs in Swift

JWTKit is a library for working with JSON Web Tokens (JWT) in Swift. It provides a simple and easy-to-use API for creating, parsing, and validating JWTs. JWTs are a popular way to authenticate information (claims) between parties. JWTKit makes it easy to work with them in your Swift projects.

Expand All @@ -21,7 +21,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4

If you inspect the token on [jwt.io](https://jwt.io), you'll see that it consists of three parts separated by dots: the header, the payload, and the signature.

> Note: don't paste production tokens into jwt.io or any other online tool, as they could be intercepted and misused.
> Note: Don't paste production tokens into jwt.io or any other online tool, as they could be intercepted and misused.
## Installing JWTKit

Expand Down Expand Up @@ -73,7 +73,7 @@ Once you have a ``JWTKeyCollection`` object, you can use it to "create" a JWT. C
In this example, we define a ``TestPayload`` struct that conforms to the ``JWTPayload`` protocol. This protocol requires us to implement the ``JWTPayload/verify(using:)`` method, which includes optional additional validation logic that can be performed when creating the JWT. In this case, we're verifying that the token has not expired.
The properties of the struct are the claims we want to include in the JWT. JWTKit provides a number of built-in claims, such as ``ExpirationClaim`` and ``IssuerClaim``, which are commonly used in JWTs. JWTKit supports the [seven registered claims](https://datatracker.ietf.org/doc/html/rfc7519#section-4.1) defined in the JWT specification, but you can also define custom claims if needed.

> Note: in the example, ``CodingKeys`` are defined to map Swift property names to the JSON keys used in the JWT. This means that in the JWT the expiration claim will be named `exp` and the issuer claim will be named `iss`, as per the JWT specification.
> Note: In the example, ``CodingKeys`` are defined to map Swift property names to the JSON keys used in the JWT. This means that in the JWT the expiration claim will be named `exp` and the issuer claim will be named `iss`, as per the JWT specification.
To create a JWT with this payload, you can create a new instance of the payload and use the key collection to sign it:

Expand All @@ -100,12 +100,19 @@ JWTs are commonly used in authentication and authorisation flows in web applicat

This flow allows you to securely transmit information between the client and server without the need for the client to store sensitive information, such as passwords, locally.

Let's put this into practice with a simple example. This example uses Swift pseudo-code to demonstrate the flow, without using a specific web framework. You can adapt this code to work with your preferred web framework.
First, we'll create a route that handles the login request. This route will receive the user's information, create a JWT with that information, and sign it with the key collection:
Let's put this into practice with a simple example. The following snippets use Swift pseudo-code to demonstrate the flow, without using a specific web framework. You can adapt this code to work with your preferred web framework.
First, we'll create a payload struct that contains the user's information:

@Snippet(path: "site/Snippets/jwt-kit", slice: auth_user_payload)

The `UserPayload` struct represents the claims we want to include in the JWT. In this example, we include the user's ID, an expiration claim, and a list of roles. The ``JWTPayload/verify(using:)`` method checks that the token has not expired and that the user has the "admin" role. The `init` method creates a new payload from a `User` object, which could be retrieved from a database, for example.
The `UserPayload` struct represents the claims we want to include in the JWT. In this snippet, we include the user's ID, an expiration claim, and a list of roles.
The ``JWTPayload/verify(using:)`` method checks that the token has not expired and that the user is an admin. The `init` method creates a new payload from a `User` object, which could be retrieved from a database, for example.
The roles claim is a custom claim:

@Snippet(path: "site/Snippets/jwt-kit", slice: auth_user_role_claim)

This is a simple struct that conforms to the ``JWTClaim`` protocol. The ``value`` property is the value of the claim, which in this case is a list of roles.
Next, we'll create a route that handles user logins and returns a JWT:

```swift
router.post("login") { req async throws -> Response in
Expand All @@ -122,7 +129,7 @@ The `login` route receives the user's username and password, finds the user in t
Next, we'll create a route that handles requests that require authentication. This route will validate the JWT in the request headers and return the requested data if the token is valid:

```swift
router.get("protected") { req async throws -> Response in
router.get("admin-protected") { req async throws -> Response in
let token = req.headers["Authorization"]
let payload = try await keyCollection.verify(token, as: UserPayload.self)
let user = User.find(payload.userID)
Expand All @@ -136,7 +143,6 @@ This should be enough to understand how JWTKit can be used to implement an authe

## Conclusion

JWTKit is a powerful library for working with JSON Web Tokens in Swift. It provides a simple and easy-to-use API for creating, parsing, and validating JWTs, making it easy to implement authentication and authorisation flows in your Swift projects. In this tutorial, you learned how to install JWTKit, create and validate JWTs, and implement an authentication and authorisation flow using JWTs. While this tutorial covered the basics of JWTKit, there are many more features and options available in the library. Check out the [JWTKit documentation](https://api.vapor.codes/jwt-kit/latest/JWTKit/index.html) for more information on how to use JWTKit in your projects.
But for now, you should be able to start using JWTs in your Swift projects with confidence!

JWTKit is a powerful library for working with JSON Web Tokens in Swift. It provides a simple and easy-to-use API for creating, parsing, and validating JWTs, making it easy to implement authentication and authorisation flows in your Swift projects. In this tutorial, you learned how to install JWTKit, create and validate JWTs, and implement an authentication and authorisation flow using JWTs. While this tutorial covered the basics of JWTKit, there are many more features and options available in the library. Check out the [JWTKit README](https://github.com/vapor/jwt-kit) for more information on how to use JWTKit in your projects.

But for now, you should be able to start using JWTs in your Swift projects with confidence!

0 comments on commit 3c4bc87

Please sign in to comment.