# JWTDecode.swift: decoding a JWT without validating it

> JWTDecode.swift is an MIT licensed Swift library that turns a JSON web token into typed properties for iOS, macOS, tvOS and watchOS, with the registered claims exposed as named properties and custom claims decodable into your own types. The judgement is that the library is explicit about its limits, it says in the first line of the readme that it does not validate the token, and the support policy is more carefully written than most.

**auth0/JWTDecode.swift** — A JWT decoder for iOS, macOS, tvOS, and watchOS

- Repository: https://github.com/auth0/JWTDecode.swift
- Website: https://auth0.github.io/JWTDecode.swift
- Stars: 710 · Forks: 129
- Language: Swift
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/auth0-jwtdecode-swift

## A decoder, and the library says so first

The first substantive line of the readme is a warning, and it is in bold: this library doesn't validate the JWT, and any well-formed JWT can be decoded from Base64URL.

That sentence decides how the library should be used. Decoding a token is Base64URL decoding plus some JSON parsing, and it tells you nothing about whether the token was signed by the party you expect. A library that quietly implied otherwise would be a security problem, so the honest framing is the only acceptable one, and it is why the library is usable next to a real verification step rather than instead of one.

The rest of the project is the standard Auth0 shape. The API documentation is auto-generated from the code comments and is published at the documentation site, with separate pages for the JWT type, the Claim type and the error type. The project also points at Auth0's own documentation, which is the logical next step for a developer who has just decoded a token and now needs to know how to get one.

The package is MIT licensed, written in Swift, and the last push to the repository was on 2026-10-15, with the newest release at 4.0.0 from 2026-02-23, after a 4.0.0 beta in February and 3.3.0 in April 2025.

The scope is narrow on purpose. It handles the token, the claims and the conversions, and nothing else, and the projects it fits are the ones where the token arrives from a library that already checked the signature.

## Three properties and a subscript

The API is small enough to hold in your head, and it is built around one idea, that a claim is accessed by name and converted to the type you want.

Decoding a token is one call:

```swift
import JWTDecode

let jwt = try decode(jwt: token)
```

The result exposes the three parts of the token, with the header as a dictionary, the claims in the body, and the signature.

The registered claims get named properties rather than subscript access, and the names are chosen to read in Swift rather than to copy the specification. The audience is the audience, the subject is the subject, the JWT id is the identifier, the issuer is the issuer, not before is the notBefore, issued at is the issuedAt, and expiration time is the expiresAt.

Custom claims go through the subscript, and the value you get back converts on demand:

```swift
if let email = jwt["email"].string {
    print("Email is \(email)")
}
```

The supported conversions are listed in the readme, a string, a boolean, an integer, a double, a date, an array of strings and data. So a claim that arrived as a number in the JSON can be read as a double, and one that arrived as a timestamp as a date, without the caller writing the conversion.

If the token is malformed, the decode function throws a JWTDecodeError, and the readme shows the catch clause, which is the whole error handling surface.

## A custom claim can decode into your own type

The part that goes beyond a fixed list of conversions is decoding a claim straight into a Decodable type, which is what you want for a claim that is an object rather than a string.

```swift
struct Address: Decodable {
    let street: String
    let city: String
}

let address = try jwt["address"].decode(Address.self)
```

And you can pass your own decoder, which is how the common snake case problem gets solved:

```swift
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let user = try jwt["user_info"].decode(User.self, using: decoder)
```

The key decoding strategy matters more here than it usually does, because token claims are conventionally snake case, the issuer_info and the user_info style, while Swift properties are camel case, and the library does not impose a convention of its own on the data.

The readme also shows the extension pattern for a claim you use in several places, a computed property on the JWT type that reaches through the subscript and converts. That is a small convenience, and it is the kind of thing a project provides because its own authors needed it in more than one file.

The note about concurrency sits in the same part of the readme: the library is fully compatible with the concurrency features in Swift 6, and all the public types conform to Sendable for safe use across concurrency boundaries. For a library that hands you a decoded value and then lets you pass it around, that conformance is the difference between a value you can send to a task and one you cannot.

## Three package managers, and one flag that matters

The installation section covers the three ways an Apple developer installs a library, and each one is a few lines.

With Swift Package Manager you add the package through the Xcode interface, opening the Add Packages menu item and pasting the repository URL into the search box, then choosing the dependency rule and pressing Add Package. The URL is the GitHub address of the repository itself, which is how a package that has no registry is added.

With CocoaPods the Podfile gets one line, pod 'JWTDecode', '~> 4.0', followed by pod install. With Carthage the Cartfile gets github "auth0/JWTDecode.swift" ~> 4.0, and the build step is carthage bootstrap --use-xcframeworks.

That last flag is the one to notice, because it is the difference between a framework that can be used from any Apple platform target and one that cannot. The library supports iOS, macOS, tvOS, watchOS and visionOS, and a library that ships as a plain static archive cannot be embedded in a watchOS or a visionOS app. Building the XCFrameworks is what makes the same source usable everywhere the readme claims.

The requirements themselves are stated at the top: iOS 15.0 or newer, macOS 12.0 or newer, tvOS 15.0 or newer, watchOS 8.0 or newer, visionOS 1.0 or newer, with Xcode 26.x and Swift 6.0.

The repository carries the artefacts for all three managers, a Package.swift for the package manager, a podspec for CocoaPods and and a Gemfile, alongside the Xcode project, a playground, the test target and a fastlane directory for the release automation.

## The support policy says a platform drop is a minor release

The support policy is the most carefully written part of the readme, and it exists because the library is a dependency of other people's applications and the platform versions move underneath it.

The readme puts a note at the top of the documentation telling you to read the policy to learn when dropping Xcode, Swift and platform versions will not be considered a breaking change. Then it answers that question three times.

For Xcode, the only supported versions are those that can currently be used to submit apps to the App Store, and once a version becomes unsupported, dropping it will not be considered a breaking change and will be done in a minor release.

For Swift, the minimum supported minor version is the one released with the oldest supported Xcode version, with the same promise.

For platforms, only the last four major versions of any platform are supported, including the current one, and dropping an old one is a minor release. The readme gives the example, iOS 15 will cease to be supported when iOS 19 is released and the library will be able to drop it in a minor release. And for macOS, the yearly named releases count as a major platform version for the purposes of the policy, regardless of the actual version numbers.

That last clause is the one that catches people, because macOS does not have major versions in the usual sense. Without it, a policy about major versions would be ambiguous on one of the four platforms the library supports.

The point of the policy is that a consumer of the library can upgrade the library without it being a breaking change, which is what lets a large application pin it and still take new versions of Xcode.

## Version 4 came with two migration guides

The 4.0 release is the recent history of the library, and the repository carries two migration documents, one for version 3 and one for version 4, which is unusual and tells you something about the audience.

A library that has been around for years, with a long-running 3.x line and a 4.x line since February 2026, has accumulated users whose code is written against the older API. Shipping a migration guide for the old version as well as the new one is the difference between a major release and a migration project, and the readme points at the version 3 guide from the top of the document with a single line.

The release sequence itself was quick. The 4.0.0 beta went out on 2026-02-04 and the final 4.0.0 on 2026-02-23, so the beta was open for a couple of weeks and the final release followed 3.3.0, which shipped in April 2025.

The repository layout is the usual shape for a Swift library with several delivery mechanisms, the sources in a JWTDecode directory, the tests in JWTDecodeTests, an Xcode project, a playground for trying the API in the editor, a podspec, the Package.swift file, a fastlane directory for the release automation, a docs directory, and a set of configuration files that say a lot about the project, a swiftlint configuration, a semgrep ignore file, a codecov configuration, an opslevel configuration file, and and a Gemfile.

There is also a security policy in the readme, and it is the one thing not to get wrong. Security vulnerabilities should not be reported on the public issue tracker, and the responsible disclosure programme is the route. For a library whose entire output is used to make trust decisions, that is the correct instruction to have in the readme.

## Conclusion

JWTDecode.swift fits an Apple platform application that receives a token and needs to read a claim or two, because the registered claims are named properties and the custom ones convert to the types you actually want, with a Decodable path for the rest. It is the wrong choice if you need signature verification, because the library decodes without validating and a forged token decodes just as happily as a real one, so the verification has to come from somewhere else. It is also the wrong choice if you need a JWT library for a non-Apple platform. Before you adopt it, decide where the signature check happens, and if you are moving from the 3.x line, read the version 4 migration guide, because the major bump in February 2026 is likely to change how your call sites look.

## FAQ

### Does JWTDecode.swift validate the token signature?

No. The readme says so in the first line, that the library does not validate the JWT and that any well-formed JWT can be decoded from Base64URL. Signature verification has to come from another component.

### How do I read a custom claim?

With a subscript and a conversion, so jwt["email"].string for a string. The supported conversions are string, boolean, integer, double, date, an array of strings and data, and a claim that holds an object can be decoded into your own Decodable type with jwt["address"].decode(Address.self), optionally with your own JSONDecoder.

### Which platforms does JWTDecode.swift support?

iOS 15.0 or newer, macOS 12.0 or newer, tvOS 15.0 or newer, watchOS 8.0 or newer and visionOS 1.0 or newer, with Xcode 26.x and Swift 6.0. All the public types conform to Sendable for use across concurrency boundaries.

### How do I install JWTDecode.swift?

Through Swift Package Manager by adding the repository URL in Xcode, with CocoaPods by adding pod 'JWTDecode', '~> 4.0' to the Podfile, or with Carthage by adding the repository to the Cartfile and running carthage bootstrap --use-xcframeworks, which is what produces the frameworks the watchOS and visionOS targets need.

## Sources

- [auth0/JWTDecode.swift on GitHub](https://github.com/auth0/JWTDecode.swift)
- [License: MIT](https://github.com/auth0/JWTDecode.swift/blob/master/LICENSE)
- [Project website](https://auth0.github.io/JWTDecode.swift)
- [README](https://github.com/auth0/JWTDecode.swift/blob/master/README.md)
- [Releases](https://github.com/auth0/JWTDecode.swift/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/auth0-jwtdecode-swift
