Swiftpack.co - Package - vapor/jwt-kit

JWTKit

Documentation Team Chat MIT License Continuous Integration Swift 5.2

๐Ÿ”‘ JSON Web Token signing and verification (HMAC, RSA, ECDSA) using BoringSSL.

Major Releases

The table below shows a list of JWTKit major releases alongside their compatible Swift versions.

|Version|Swift|SPM| |---|---|---| |4.0|5.2+|from: "4.0.0"| |3.0|4.0+|from: "3.0.0"| |2.0|3.1+|from: "2.0.0"| |1.0|3.1+|from: "1.0.0"|

Use the SPM string to easily include the dependendency in your Package.swift file.

.package(url: "https://github.com/vapor/jwt-kit.git", from: "4.0.0")

Note: Prior to version 4.0, this package was included in vapor/jwt.git.

Supported Platforms

JWTKit supports the following platforms:

  • Ubuntu 16.04, 18.04, 20.04
  • macOS 10.15, 11
  • CentOS 8
  • Amazon Linux 2

Overview

JWTKit provides APIs for signing and verifying JSON Web Tokens (RFC7519). It supports the following features:

  • Verifying (parsing)
  • Signing (serializing)
  • RSA (RS256, RS384, RS512)
  • ECDSA (ES256, ES384, ES512)
  • HMAC (HS256, HS384, HS512)
  • Claims (aud, exp, iss, etc)
  • JSON Web Keys (JWK, JWKS)

This package ships a private copy of BoringSSL for cryptography.

Vapor

If you are using Vapor, check out the JWT package which makes it easier to configure and use JWTKit in your project.

Getting Started

To start verifying or signing JWT tokens, you will need an instance of JWTSigners.

import JWTKit

// Signs and verifies JWTs
let signers = JWTSigners()

Let's add a simple HS256 signer for testing. HMAC signers can sign and verify tokens.

// Add HMAC with SHA-256 signer.
signers.use(.hs256(key: "secret"))

For this example, we'll use the very secure key secret.

Verifying

Let's try to verify the following example JWT.

let jwt = """
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ2YXBvciIsImV4cCI6NjQwOTIyMTEyMDAsImFkbWluIjp0cnVlfQ.lS5lpwfRNSZDvpGQk6x5JI1g40gkYCOWqbc3J_ghowo
"""

You can inspect the contents of this token by visiting jwt.io and pasting the token in the debugger. Set the key in the "Verify Signature" section to secret.

We need to create a struct conforming to JWTPayload that represents the JWT's structure. We'll use JWTKit's included claims to handle common fields like sub and exp.

// JWT payload structure.
struct TestPayload: JWTPayload, Equatable {
    // Maps the longer Swift property names to the
    // shortened keys used in the JWT payload.
    enum CodingKeys: String, CodingKey {
        case subject = "sub"
        case expiration = "exp"
        case isAdmin = "admin"
    }

    // The "sub" (subject) claim identifies the principal that is the
    // subject of the JWT.
    var subject: SubjectClaim

    // The "exp" (expiration time) claim identifies the expiration time on
    // or after which the JWT MUST NOT be accepted for processing.
    var expiration: ExpirationClaim

    // Custom data.
    // If true, the user is an admin.
    var isAdmin: Bool

    // Run any additional verification logic beyond
    // signature verification here.
    // Since we have an ExpirationClaim, we will
    // call its verify method.
    func verify(using signer: JWTSigner) throws {
        try self.expiration.verifyNotExpired()
    }
}

Now that we have a JWTPayload, we can use JWTSigners to parse and verify the JWT.

// Parses the JWT and verifies its signature.
let payload = try signers.verify(jwt, as: TestPayload.self)
print(payload)

If everything worked, you should see the payload printed:

TestPayload(
    subject: "vapor", 
    expiration: 4001-01-01 00:00:00 +0000, 
    isAdmin: true
)

Signing

We can also generate JWTs, also known as signing. To demonstrate this, let's use the TestPayload from the previous section.

// Create a new instance of our JWTPayload
let payload = TestPayload(
    subject: "vapor",
    expiration: .init(value: .distantFuture),
    isAdmin: true
)

Then, pass the payload to JWTSigners.sign.

// Sign the payload, returning a JWT.
let jwt = try signers.sign(payload)
print(jwt)

You should see a JWT printed. This can be fed back into the verify method to access the payload.

JWK

A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key (RFC7517). These are commonly used to supply clients with keys for verifying JWTs.

For example, Apple hosts their Sign in with Apple JWKS at the following URL.

GET https://appleid.apple.com/auth/keys

You can add this JSON Web Key Set (JWKS) to your JWTSigners.

import Foundation
import JWTKit

// Download the JWKS.
// This could be done asynchronously if needed.
let jwksData = try Data(
    contentsOf: URL(string: "https://appleid.apple.com/auth/keys")!
)

// Decode the downloaded JSON.
let jwks = try JSONDecoder().decode(JWKS.self, from: jwksData)

// Create signers and add JWKS.
let signers = JWTSigners()
try signers.use(jwks: jwks)

You can now pass JWTs from Apple to the verify method. The key identifier (kid) in the JWT header will be used to automatically select the correct key for verification.

Note: As of writing, JWK only supports RSA keys.

HMAC

HMAC is the simplest JWT signing algorithm. It uses a single key that can both sign and verify tokens. The key can be any length.

  • hs256: HMAC with SHA-256
  • hs384: HMAC with SHA-384
  • hs512: HMAC with SHA-512
// Add HMAC with SHA-256 signer.
signers.use(.hs256(key: "secret"))

RSA

RSA is the most commonly used JWT signing algorithm. It supports distinct public and private keys. This means that a public key can be distributed for verifying JWTs are authentic while the private key that generates them is kept secret.

To create an RSA signer, first initialize an RSAKey. This can be done by passing in the components.

// Initialize an RSA key with components.
let key = RSAKey(
    modulus: "...",
    exponent: "...",
    // Only included in private keys.
    privateExponent: "..."
)

You can also choose to load a PEM file:

let rsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0cOtPjzABybjzm3fCg1aCYwnx
PmjXpbCkecAWLj/CcDWEcuTZkYDiSG0zgglbbbhcV0vJQDWSv60tnlA3cjSYutAv
7FPo5Cq8FkvrdDzeacwRSxYuIq1LtYnd6I30qNaNthntjvbqyMmBulJ1mzLI+Xg/
aX4rbSL49Z3dAQn8vQIDAQAB
-----END PUBLIC KEY-----
"""

// Initialize an RSA key with public pem.
let key = RSAKey.public(pem: rsaPublicKey)

Use .private for loading private RSA pem keys. These start with:

-----BEGIN RSA PRIVATE KEY-----

Use .certificate for loading X.509 certificates. These start with:

-----BEGIN CERTIFICATE-----

Once you have the RSAKey, you can use it to create an RSA signer.

  • rs256: RSA with SHA-256
  • rs384: RSA with SHA-384
  • rs512: RSA with SHA-512
// Add RSA with SHA-256 signer.
try signers.use(.rs256(key: .public(pem: rsaPublicKey)))

ECDSA

ECDSA is a more modern algorithm that is similar to RSA. It is considered to be more secure for a given key length than RSA1. However, you should do your own research before deciding.

Like RSA, you can load ECDSA keys using PEM files:

let ecdsaPublicKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2adMrdG7aUfZH57aeKFFM01dPnkx
C18ScRb4Z6poMBgJtYlVtd9ly63URv57ZW0Ncs1LiZB7WATb3svu+1c7HQ==
-----END PUBLIC KEY-----
"""

// Initialize an ECDSA key with public pem.
let key = ECDSAKey.public(pem: ecdsaPublicKey)

Use .private for loading private ECDSA pem keys. These start with:

-----BEGIN PRIVATE KEY-----

Once you have the ECDSAKey, you can use it to create an ECDSA signer.

  • es256: ECDSA with SHA-256
  • es384: ECDSA with SHA-384
  • es512: ECDSA with SHA-512
// Add ECDSA with SHA-256 signer.
try signers.use(.es256(key: .public(pem: ecdsaPublicKey)))

Claims

JWTKit includes several helpers for implementing common JWT claims.

|Claim|Type|Verify Method| |---|---|---| |aud|AudienceClaim|verifyIntendedAudience(includes:)| |exp|ExpirationClaim|verifyNotExpired(currentDate:)| |jti|IDClaim|n/a| |iat|IssuedAtClaim|n/a| |iss|IssuerClaim|n/a| |locale|LocaleClaim|n/a| |nbf|NotBeforeClaim|verifyNotBefore(currentDate:)| |sub|SubjectClaim|n/a|

All claims should be verified in the JWTPayload.verify method. If the claim has a special verify method, you can use that. Otherwise, access the value of the claim using value and check that it is valid.


This package was originally authored by the wonderful @siemensikkema.


1: https://sectigostore.com/blog/ecdsa-vs-rsa-everything-you-need-to-know/

Github

link
Stars: 23

Dependencies

Used By

Total: 0

Releases

Add `ECDSAKey` support to `JWKS` - 2020-09-08 21:13:26

This patch was authored and released by @JaapWijnen.
  • Allows to create JWK's using ECDSAKeys
  • Allows use of ECDSAKeys with JWTSigner.
  • Prevents a crash when using the .es384 curve for ECDSAKeys
let key = ECDSAKey.generate(curve: .p384)
let jwks = JWKS(keys: [
   JWK.ecdsa(.es384, identifier: JWKIdentifier(string: "token-kid"), x: key.parameters!.x, y: key.parameters!.y, curve: .p384)
])
let key = ECDSAKey.generate(curve: .p384)
let signer = JWTSigner.es384(key: key)

Add RSAKey support for x509 certificate - 2020-08-20 18:34:27

This patch was authored and released by @tanner0101.

Adds support for initializing RSAKey from X.509 certificate pem files (#42).

// Create signer from X.509 certificate
let signer = try JWTSigner.rs256(key: .certificate(pem: ...))

Set default signer when using JWKs - 2020-07-30 15:16:01

This patch was authored and released by @tanner0101.

Fixes an issue causing no default signer to be set when configuring only JWKs (#39).

JWTKit 4.0.0 - 2020-07-29 21:02:08

This patch was authored and released by @tanner0101.

Docs: https://github.com/vapor/jwt-kit

More information on Vapor 4 official release: https://forums.swift.org/t/vapor-4-official-release-begins/34802

Add support for Microsoft JWTs - 2020-07-29 18:17:47

This patch was authored and released by @tanner0101.

Adds new MicrosoftIdentityToken that supports Microsoft's JWT format (#36).

Support JWKs with no algorithm - 2020-07-29 18:06:29

This patch was authored and released by @tanner0101.

Adds support for JSON Web Keys that do not specify an algorithm (#34).

JWKs are now stored directly in JWTSigners via the use(jwk:) method. When a request to verify a JWT is received that matches a JWK, both the JWT and the JWK's algorithm header is checked. A signer is then created on the fly to handle the request.

Add support for Apple Silicon - 2020-07-17 15:50:28

This patch was authored by @0xTim and released by @tanner0101.

Adds support for Apple Silicon (#32).

Boring SSL update - 2020-07-17 15:15:40

This patch was authored by @0xTim and released by @tanner0101.

Brings in the latest version of BoringSSL (#31).

Add typ, cty fields in the JWT header - 2020-06-25 18:10:38

This patch was authored by @Maxim-Inv and released by @tanner0101.

Adds typ and cty fields to the JWT header (#25, fixes #5).

  • Add typ header as parameter making "JWT" the default.

  • Allow setting cty during JWT signing.

let signer = JWTSigner.hs256(key: keyData)`
let token = try signer.sign(payload, cty: "twilio-fpa;v=1")

Migrate HMAC to Crypto - 2020-04-16 14:45:39

This patch was authored and released by @0xTim.

Migrate all the HMAC code away from BoringSSL to Swift Crypto (#24).

Create a generic `JWTMultiValueClaim` protocol - 2020-03-23 21:52:48

This patch was authored and released by @gwynne.
  • Allows much simpler implementation of JWT claims with multiple values.
  • Switches to this implementation for AudienceClaim

Give `AudienceClaim` more powers - 2020-03-16 22:53:43

This patch was authored and released by @gwynne.
  • Allow AudienceClaim to represent more than one audience, per RFC 7519 ยง 4.1.3
  • Add verifyIntendedAudience(includes:) method for conveniently verifying that the audience is the intended one.
  • Add unit tests for new functionality.

Because the Value type of AudienceClaim is now [String] instead of String, this change is API-breaking!

This would normally require a semver-major update, but as the package is still in RC, I think it can scrape past with a semver-minor, especially since the change is to obey spec.

Added the nonce_supported claim to AppleIdentityToken - 2020-03-04 16:29:06

The claim was missing.

This patch was authored and released by @grosch.

Release Candidate 1 - 2020-02-28 23:30:58

Updates to Swift 5.2.

Release candidates represent the final shift toward focusing on bug fixes and documentation. Breaking changes will only be accepted for critical issues. We expect a final release of this package shortly after Swift 5.2's release date.

Fixes Codable support for LocaleClaim - 2020-02-26 21:13:35

Previously, LocaleClaims were not decoded from JWTs correctly. This patch fixes their behavior and adds tests to prevent regression.

Fix ECDSA signature size - 2020-02-21 18:37:22

Correctly serializes ECDSA signature R / S values according to elliptic curve.

Before this patch, the code assumes R / S values will be 32 bytes. This is true for P-256 and P-384 keys, but is not true for P-521 keys. P-521 keys result in 66 byte R / S values.

From https://tools.ietf.org/html/rfc7515#appendix-A.3:

Section on ES256

We need to split the 64 member octet sequence of the JWS Signature

Section on ES512

We need to split the 132-member octet sequence of the JWS Signature into two 66-octet sequences

Note that the curve name (i.e., P-521) is different than the JWT algorithm name (i.e., ES512). The 512 in ES512 refers to SHA512. Also note that P-521 != 512, that's not a typo.

In order to support the different key curves dynamically, the signer now gets the curve name from the key during signing and verification. This curve is used to determine how wide the R / S values should be.

Remove OpenSSL - 2020-02-21 17:36:47

Removes OpenSSL and migrates calls to our own vendored BoringSSL (#14, #13).

BoolClaim handles strings - 2020-02-18 00:18:47

BoolClaim wasn't properly handling "true" and "false" as strings.

Apple/Google identity tokens - 2020-02-15 18:24:05

Adds commonly used AppleIdentityToken and GoogleIdentityToken structs. This includes code necessary to validate the apple/google provided identity data against their respective JWKS elements.

This patch also restructures the project a bit to move classes into their own files with hierarchical folders to make it easier to find the items you're looking for.

JWKS.find - 2020-02-13 21:11:03

Adds new methods for finding JWKs from by identifier and type (#10).

let jwks: JWKS = ...
let signInWithApple = jwks.find(identifier: "AIDOPK1", type: .rsa)

Fix Payload Verification - 2020-01-07 18:10:49

JWT verification using JWTSigner and JWTSigners now correctly invokes JWTPayload's optional verify method. (#6)

JWTKit 4.0.0 Beta 2.1 - 2019-12-10 02:33:15

  • Replaced all fatalErrors with Swift errors.

JWTKit 4.0.0 Beta 2 - 2019-12-09 16:40:56

  • Separated from vapor/jwt into new repo
  • Removed JWT type and split functionality into JWTSigner and JWTSigners
  • New internal JWTParser and JWTSerializer types to reduce code dupe