Full Code of pauldijou/jwt-scala for AI

main bb5348679d4e cached
93 files
293.7 KB
85.9k tokens
1 requests
Download .txt
Showing preview only (322K chars total). Download the full file or copy to clipboard to get everything.
Repository: pauldijou/jwt-scala
Branch: main
Commit: bb5348679d4e
Files: 93
Total size: 293.7 KB

Directory structure:
gitextract_3pvve9sy/

├── .git-blame-ignore-revs
├── .github/
│   └── workflows/
│       ├── docs.yml
│       ├── release.yml
│       └── tests.yml
├── .gitignore
├── .scala-steward.conf
├── .scalafmt.conf
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sbt
├── core/
│   ├── jvm/
│   │   └── src/
│   │       └── test/
│   │           └── scala/
│   │               ├── JwtSpec.scala
│   │               └── JwtUtilsSpec.scala
│   └── shared/
│       └── src/
│           ├── main/
│           │   └── scala/
│           │       ├── Jwt.scala
│           │       ├── JwtAlgorithm.scala
│           │       ├── JwtArrayUtils.scala
│           │       ├── JwtBase64.scala
│           │       ├── JwtClaim.scala
│           │       ├── JwtCore.scala
│           │       ├── JwtException.scala
│           │       ├── JwtHeader.scala
│           │       ├── JwtOptions.scala
│           │       ├── JwtTime.scala
│           │       └── JwtUtils.scala
│           └── test/
│               └── scala/
│                   ├── Fixture.scala
│                   ├── JwtBase64Spec.scala
│                   └── JwtClaimSpec.scala
├── docs/
│   └── src/
│       └── main/
│           ├── paradox/
│           │   ├── index.md
│           │   ├── jwt-argonaut.md
│           │   ├── jwt-circe.md
│           │   ├── jwt-core/
│           │   │   ├── index.md
│           │   │   ├── jwt-claim-private.md
│           │   │   ├── jwt-claim.md
│           │   │   ├── jwt-ecdsa.md
│           │   │   └── jwt-header.md
│           │   ├── jwt-json4s.md
│           │   ├── jwt-play-json.md
│           │   ├── jwt-play-jwt-session.md
│           │   ├── jwt-upickle.md
│           │   ├── jwt-zio-json.md
│           │   └── project/
│           │       └── build.properties
│           └── scala/
│               ├── JwtArgonautDoc.scala
│               ├── JwtCirceDoc.scala
│               ├── JwtJson4sDoc.scala
│               ├── JwtPlayJsonDoc.scala
│               ├── JwtPlayJwtSessionDoc.scala
│               ├── JwtUpickleDoc.scala
│               └── JwtZioDoc.scala
├── json/
│   ├── argonaut/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtArgonaut.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── ArgonautFixture.scala
│   │               └── JwtArgonautSpec.scala
│   ├── circe/
│   │   ├── jvm/
│   │   │   └── src/
│   │   │       └── test/
│   │   │           └── scala/
│   │   │               ├── CirceFixture.scala
│   │   │               └── JwtCirceSpec.scala
│   │   └── shared/
│   │       └── src/
│   │           └── main/
│   │               └── scala/
│   │                   └── JwtCirce.scala
│   ├── common/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtJsonCommon.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── JsonCommonFixture.scala
│   │               └── JwtJsonCommonSpec.scala
│   ├── json4s-common/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtJson4sCommon.scala
│   │       └── test/
│   │           └── scala/
│   │               └── Json4sCommonFixture.scala
│   ├── json4s-jackson/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson4sImplicits.scala
│   │       │       └── JwtJson4sJackson.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── Json4sJacksonFixture.scala
│   │               └── Json4sJacksonSpec.scala
│   ├── json4s-native/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson4sImplicits.scala
│   │       │       └── JwtJson4sNative.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── Json4sNativeFixture.scala
│   │               └── Json4sNativeSpec.scala
│   ├── play-json/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson.scala
│   │       │       └── JwtJsonImplicits.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── JsonFixture.scala
│   │               └── JwtJsonSpec.scala
│   ├── upickle/
│   │   ├── jvm/
│   │   │   └── src/
│   │   │       └── test/
│   │   │           └── scala/
│   │   │               ├── JwtUpickleFixture.scala
│   │   │               └── JwtUpickleSpec.scala
│   │   └── shared/
│   │       └── src/
│   │           └── main/
│   │               └── scala/
│   │                   ├── JwtUpickle.scala
│   │                   └── JwtUpickleImplicits.scala
│   └── zio-json/
│       └── src/
│           ├── main/
│           │   └── scala/
│           │       └── JwtZIOJson.scala
│           └── test/
│               └── scala/
│                   ├── JwtZIOJsonSpec.scala
│                   └── ZIOJsonFixture.scala
├── play/
│   └── src/
│       ├── main/
│       │   └── scala/
│       │       ├── JwtPlayImplicits.scala
│       │       └── JwtSession.scala
│       └── test/
│           └── scala/
│               ├── JwtResultSpec.scala
│               ├── JwtSessionAsymetricSpec.scala
│               ├── JwtSessionCustomDifferentNameSpec.scala
│               ├── JwtSessionCustomSpec.scala
│               ├── JwtSessionSpec.scala
│               └── PlayFixture.scala
├── project/
│   ├── Libs.scala
│   ├── build.properties
│   └── plugins.sbt
└── scripts/
    ├── bump.sh
    ├── clean.sh
    └── pu.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .git-blame-ignore-revs
================================================
# Scala Steward: Reformat with scalafmt 3.7.2
e8c410d04442fe9ac7aa50df34a398972a602cdc

# Scala Steward: Reformat with scalafmt 3.7.17
14d9145808edddb1d80975faf427882b2e081e03

# Scala Steward: Reformat with scalafmt 3.8.3
7013c976689533b3513842d28925048c75cb592e

# Scala Steward: Reformat with scalafmt 3.9.7
427d1611771af19de6fd3a56f78c60b0ea18e910


================================================
FILE: .github/workflows/docs.yml
================================================
name: Docs
on:
  push:
    tags: ["*"]
concurrency:
  group: docs

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 11
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - name: "Get latest tag"
        id: previoustag
        uses: "WyriHaximus/github-action-get-previous-tag@v1"
      - name: Build
        run: sbt 'set docs/version := "${{ steps.previoustag.outputs.tag }}".drop(1)' docs/makeSite
      - name: setup git config
        run: |
          git config user.name "GitHub Actions Bot"
          git config user.email "<>"
      - name: Deploy
        run: |
          git checkout --orphan gh-pages
          git add -f docs/target/site
          git commit -m "Rebuild GitHub pages"
          git filter-branch -f --prune-empty --subdirectory-filter docs/target/site
          git push -f origin gh-pages


================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
  push:
    branches: [master, main]
    tags: ["*"]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 8
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - run: sbt ci-release
        env:
          PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
          PGP_SECRET: ${{ secrets.PGP_SECRET }}
          SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
          SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}


================================================
FILE: .github/workflows/tests.yml
================================================
name: CI

on:
  pull_request:
    branches: [master, main]
  push:
    branches: [master, main]

env:
  SCALA212: 2.12.20
  SCALA213: 2.13.14
  SCALA3: 3.3.0

jobs:
  linting:
    runs-on: ubuntu-latest
    steps:
      - uses: "actions/checkout@v3"
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 11
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - name: Checking code formatting
        run: sbt formatCheck
  docs-check:
    runs-on: ubuntu-latest
    steps:
      - uses: "actions/checkout@v3"
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 11
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - name: Check the documentation
        run: sbt docs/makeSite
  mima:
    runs-on: ubuntu-latest
    steps:
      - uses: "actions/checkout@v3"
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 11
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - name: Report binary issues
        run: "sbt ${{ matrix.project }}/mimaReportBinaryIssues"
    strategy:
      matrix:
        project:
          - coreJVM
          - coreJS
          - coreNative
          - playJson
          - playFramework
          - circeJVM
          - circeJS
          - circeNative
          - upickleJVM
          - upickleJS
          - upickleNative
          - json4sNative
          - json4sJackson
          - argonaut
          - zioJson
  tests:
    runs-on: ubuntu-latest
    name: Tests ${{ matrix.project }} (${{ matrix.scala }})
    steps:
      - uses: "actions/checkout@v3"
      - uses: actions/setup-java@v3
        with:
          distribution: temurin
          java-version: 11
          cache: sbt
      - uses: sbt/setup-sbt@v1
      - name: Test
        run: "sbt ++${{ matrix.scala }} ${{ matrix.project }}/test"
    strategy:
      matrix:
        exclude:
          - project: playFramework
            scala: $SCALA212
        project:
          - coreJVM
          - coreJS
          - coreNative
          - playJson
          - playFramework
          - circeJVM
          - circeJS
          - circeNative
          - upickleJVM
          - upickleJS
          - upickleNative
          - json4sNative
          - json4sJackson
          - argonaut
          - zioJson
        scala:
          - $SCALA212
          - $SCALA213
          - $SCALA3


================================================
FILE: .gitignore
================================================
.history
target
project/project
project/target
.bloop/
.idea
.bsp
.metals/
.vscode/
metals.sbt

================================================
FILE: .scala-steward.conf
================================================
updates.ignore = [
  { groupId = "com.google.inject", artifactId = "guice" }
]


================================================
FILE: .scalafmt.conf
================================================
version=3.10.7
runner.dialect=scala213

maxColumn = 100

rewrite.rules = [Imports, AvoidInfix, SortModifiers, PreferCurlyFors]
rewrite.imports.sort = ascii
rewrite.imports.groups = [
  ["java\\..*", "javax\\..*", "scala\\..*"]
]


================================================
FILE: CHANGELOG.md
================================================
# Changelog

Note: this file is no longer updated, check the [releases tab](https://github.com/jwt-scala/jwt-scala/releases)
for details about each version.

## 6.0.0 (26/02/2021)

Important: the groupId changed from `fr.pauldijou` to `com.github.jwt-scala`,
so you need to update your dependencies:

```
libraryDependencies += "com.github.jwt-scala" %% "<artifact>" % "6.0.0"
```

- Upgrade Play to 2.8.7
- Upgrade Play Json to 2.9.2
- Upgrade uPickle to 1.2.3
- Upgrade Argonaut to 6.3.3
- Upgrade Bouncycastle to 1.68
- Drop support for Scala 2.11

## 5.0.0 (31/10/2020)

- Make `JwtException` a proper exception (thanks @tpolecat)
- Update SBT and Scala version (thanks @erwan)
- Improve string splitting performance (thanks @jfosback)
- **Breaking** (a little): `JwtSession` should always have an expiration now if you have set a `play.http.session.maxAge`. Before, a few ways to create the session would forget to add it.
- **Breaking** (also a little): calling `refreshJwtSession` on a Play Result will now truly refresh and set a session if there was one to begin with. Before, it would always set a session with an expiration even if there was nothing.
- **Breaking** (maybe, maybe not, unsure): renamed KeyFactory algorithm from "ECDSA" to "EC" to better comply with [Java Security Standard Algorithm Names](https://docs.oracle.com/en/java/javase/14/docs/specs/security/standard-names.html#keyfactory-algorithms), this might impact curve names, check [ParameterSpec Names](https://docs.oracle.com/en/java/javase/14/docs/specs/security/standard-names.html#parameterspec-names) if you are impacted.

## 4.3.0 (29/02/2020)

- Add support for asymmetric algorithms for Play framerwork (thanks @Bangalor)
- Upgrade Circe to 0.13.0 (thanks @howyp)
- Upgrade Play and play-json to 2.8.0
- Upgrade upickle to 0.9.5

## 4.2.0 (03/11/2019)

- No longer fail on unknown algorithm when `signature` is `false` on options (thanks @Baccata)
- Upgrade upickle to 0.8.0 (thanks @vic)

## 4.1.0 (22/09/2019)

- Upgrade to Circe 0.12.1 (thanks @erwan)

## 4.0.0 (26/08/2019)

This is not really a breaking change release but I did some small adjustements that might break in very specific cases so not taking chances.

- Support Scala 2.13 for Play framework.
- Revert Circe to 0.11.1. After consideration, it was probably a mistake to use a Release Candidate version, I should stick to official stable releases.
- Fix an issue in `Jwt` pure Scala implementation around regexp. Again, try not to use this one, mostly for tests and demos.
- Fix examples.

## 3.1.0 (30/06/2019)

- If claim.audience is only one item, it will be stringified as a simple string compared to an array if several values. (thanks @msinton)

## 3.0.1 (16/06/2019)

- Fix support for Java 8. (thanks @brakthehack)
- Improve support for Scala 2.13. (thanks @erwan)

## 3.0.0 (09/06/2019)

- Allow override of the system clock, remove jmockit from tests. (thanks @Ophirr33)
- `JwtHeader` and `JwtClaim` are no longer `case class` so that you can extend them. (thanks @fahman)
- Fix Play demo app. (thanks @ma3574)
- Remove dependency on Bouncycastle. (thanks @brakthehack)
- Deprecate (but also improved) the pure Scala implementation of `JwtCore`. It's very limited, non-performant and should not be used. I will keep it around for tests and if some people need it.

## 2.1.0 (24/02/2019)

- Upgrade to play-json 2.7.1 (thanks @etspaceman)
- Add Scala 2.13.0-M5 to cross compilation for projects supporting it (thanks @2m and @ennru)
- Move JwtHeader and JwtClaim to basic classes (thanks @fahman)

## 2.0.0 (13/02/2019)

- Upgrade to Play 2.7.0 (thanks @prakhunov)
- Upgrade to play-json 2.7.0 (thanks @etspaceman)
- Drop support for Java 6 and 7

## 1.1.0 (09/01/2019)

- Upgrade to uPickle 0.7.1 (thanks @edombowsky)
- Add support for Argonaut (thanks @isbodand)

## 1.0.0 (25/11/2018)

- Bump bouncyCastle version to fix [CVE-2018-1000613](cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000613) (thanks @djamelz)
- Also 1.0.0 for no reason except no feature was needed over the last months.

## 0.19.0 (20/10/2018)

**Breaking change**

This is actually a simple one but still... fixed a typo at `asymmetric` missing one `m`, just need to rename a few types to fix your code (thanks @DrPhil).

- Add support to `spray-json` (thanks @Slakah)
- Bump some versions (thanks @vhiairrassary)

## 0.18.0 (09/10/2018)

- Add support to `aud` being a simple string on uPickle (thanks @deterdw)
- Make all `parseHeader` and `parseClaim` methods public.

## 0.17.0 (29/07/2018)

- After consideration, release #84 , which mostly allow users to write custom parsers by extending jwt-scala ones. Doc page can be found here.

## 0.16.0 (05/03/2018)

- Adding Key ID property to JwtHeader as `kid` in JSON payload

## 0.15.0 (24/02/2018)

- Upgrade to uPickle 0.5.1
- Upgrade to Circe 0.9.1 (thanks @jan0sch)

## 0.14.1 (30/10/2017)

- Fix exception when `play.http.session.maxAge` is `null` in Play 2.6.x (thanks @austinpernell)

## 0.14.0 (07/07/2017)

- Add `play.http.session.jwtResponseName` to customize response header in Play (thanks @Isammoc)
- Fix code snippet style in docs

## 0.13.0 (08/06/2017)

- Upgrade to Circe 0.8.0 (thanks @dvic)
- Play 2.6 support (thanks @perotom)
- Bouncy Castle 1.57 (thanks @rwhitworth)

## 0.12.1 (29/03/2017)

- Support spaces in JSON for pure Scala JWT

## 0.12.0 (20/02/2017)

- **Breaking changes** I liked having all implicits directly inside the package object but it started to create problems. When generating the documentation, which depends on all projects, we had runtime errors while all tests were green, but they are ran on project at a time. Also, it means all implicits where always present on the scope which might not be the best option. So the idea is to move them from the package object to the `JwtXXX` object. For example, for Play Json:

```scala
// Before
// JwtJson.scala.
package pdi.jwt

object JwtJson extends JwtJsonCommon[JsObject] {
  // stuff...
}

// package.scala
package pdi

package object jwt extends JwtJsonImplicits {}

// --------------------------------------------------------
// After
// JwtJson.scala.
package pdi.jwt

object JwtJson extends JwtJsonCommon[JsObject] with JwtJsonImplicits {
  // stuff...
}
```

## 0.11.0 (19/02/2017)

- Drop Scala 2.10
- Play support is back

## 0.10.0 (02/02/2017)

- Support Scala 2.12.0
- Drop Play Framework support until it supports Scala 2.12
- Add uPickle support (thanks @alonsodomin)
- Update Play Json to 2.6.0-M1 for Scala 2.12 support
- Update Circe to 0.7.0

## 0.9.2 (10/11/2016)

- Support Circe 0.6.0 (thanks @TimothyKlim )

## 0.9.1 (10/11/2016)

- Support Json4s 3.5.0 (thanks @sanllanta)

## 0.9.0 (08/10/2016)

- Transformation of Signature to ASN.1 DER for ECDSA Algorithms (thanks @bestehle)
- Remove algorithm aliases to align with [JWA spec](https://tools.ietf.org/html/rfc7518#section-3.1)

## 0.8.1 (04/09/2016)

- Update to Circe 0.5.0

## 0.8.0 (05/07/2016)

- Update to Circe 0.4.1
- `audience` is now `Set[String]` rather than just `String` inside `Claim` according to JWT spec. API using `String` still available.
- Use `org.bouncycastle.util.Arrays.constantTimeAreEqual` to check signature rather than home made function.
- Remove Play Legacy since Play 2.5+ only supports Java 1.8+

## 0.7.1 (20/04/2016)

Add `leeway` support in `JwtOptions`

## 0.7.0 (17/03/2016)

Support for Circe 0.3.0

## 0.6.0 (09/03/2016)

Support for Play Framework 2.5.0

## 0.5.1 (05/03/2016)

Fix bug not-escaping quotation mark `"` when stringifying JSON.

## 0.5.0 (31/12/2015)

### Circe support

Thanks to @dwhitney , `JWT Scala` now has support for [Circe](https://github.com/travisbrown/circe). Check out [samples](http://pauldijou.fr/jwt-scala/samples/jwt-circe/) and [Scaladoc](http://pauldijou.fr/jwt-scala/api/latest/jwt-circe/).

### Disable validation

When decoding, `JWT Scala` also performs validation. If you need to decode an invalid token, you can now use a `JwtOptions` as the last argument of any decoding function to disable validation checks like expiration, notBefore and signature. Read the **Options** section of the [core sample](http://pauldijou.fr/jwt-scala/samples/jwt-core/) to know more.

### Fix null session in Play 2.4

Since 2.4, Play assign `null` as default value for some configuration keys which throw a `ConfigException.Null` in TypeSafe config lib. This should be fixed with the new configuration system at some point in the future. In the mean time, all calls reading the configuration will be wrapped in a try/catch to prevent that.

## 0.4.1 (30/09/2015)

Fix tricky bug inside all JSON libs not supporting correctly the `none` algorithm.

## 0.4.0 (24/07/2015)

Thanks a lot to @drbild for helping review the code around security vulnerabilities.

### Now on Maven

All the sub-projects are now released directly on Maven Central. Since Sonatype didn't accept `pdi` as the groupId, I had to change it to `com.pauldijou`. Sorry about that, you will need to quickly update your `build.sbt` (or whatever file contains your dependencies).

### Breaking changes

**Good news** Those changes don't impact the `jwt-play` lib, only low level APIs.

All decoding and validating methods with a `key: String` are now removed for security reasons. Please use their counterpart which now needs a 3rd argument corresponding to the list of algorithms that the token can be signed with. This list cannot mix HMAC and asymetric algorithms (like RSA or ECDSA). This is to prevent a server using RSA with a String key to receive a forged token signed with a HMAC algorithm and the RSA public key to be accepted using the same RSA public key as the HMAC secret key by default. You can learn more by reading [this article](https://www.timmclean.net/2015/03/31/jwt-algorithm-confusion.html).

```scala
// Before
val claim = Jwt.decode(token, key)

// After (knowing that you only expect a HMAC 256)
val claim = Jwt.decode(token, key, Seq(JwtAlgorithm.HS256))
// After (supporting all HMAC algorithms)
val claim = Jwt.decode(token, key, JwtAlgorithm.allHmac)
```

If you are using `SecretKey` or `PublicKey`, the list of algorithms is optional and will be automatically computed (using `JwtAlgorithm.allHmac` and `JwtAlgorithm.allAsymetric` respesctively) but feel free to provide you own list if you want to restrict the possible algorithms. More security never killed any web application.

Why not deprecate them? I considered doing that but I decided to enforce the security fix. I'm pretty sure that most people only use one HMAC algorithm with a String key and it will force them to edit their code but it should be a minor edit since you usually only decode tokens once or twice inside a code base. The fact that the project is still very new and at a `0.x` version played in the decision.

### Fixes

Fix a security vulnerability around timing attacks.

### Features

Add implicit class to convert `JwtHeader` and `JwtClaim` to `JsValue` or `JValue`. See [examples for Play JSON](http://pauldijou.fr/jwt-scala/samples/jwt-play-json/) or [examples for Json4s](http://pauldijou.fr/jwt-scala/samples/jwt-json4s/).

```scala
// Play JSON
JwtHeader(JwtAlgorithm.HS256).toJsValue
JwtClaim().by("me").to("you").about("something").issuedNow.startsNow.expiresIn(15).toJsValue

// Json4s
JwtHeader(JwtAlgorithm.HS256).toJValue
JwtClaim().by("me").to("you").about("something").issuedNow.startsNow.expiresIn(15).toJValue
```

## 0.2.1 (24/07/2015)

Same as `0.4.0` but targeting Play 2.3

## 0.3.0 (08/06/2015)

### Breaking changes

- move exceptions to their own package
- move algorithms to their own package

### Features

- support Play 2.4.0

## 0.2.0 (02/06/2015)

### Breaking changes

- removed all `Option` from API. Now, it's either nothing or a valid key. It shouldn't have a big impact since the majority of users were using valid keys already.
- when decoding a token to a `Tuple3`, the last part representing the signature is now a `String` rather than an `Option[String]`.

### New features

- full support for `SecretKey` for HMAC algorithms
- full support for `PrivateKey` and `PublicKey` for RSA and ECDSA algorithms
- Nearly all API now have 4 possible signatures (note: `JwtAsymetricAlgorithm` is either a RSA or a ECDSA algorithm)
  - `method(...)`
  - `method(..., key: String, algorithm: JwtAlgorithm)`
  - `method(..., key: SecretKey, algorithm: JwtHmacAlgorithm)`
  - `method(..., key: PrivateKey/PublicKey, algorithm: JwtAsymetricAlgorithm)`

Use `PrivateKey` when encoding and `PublicKey` when decoding or verifying.

### Bug fixes

- Some ECDSA algorithms were extending the wrong super-type
- `{"algo":"none"}` header was incorrectly supported

## 0.1.0 (18/05/2015)

No code change from 0.0.6, just more doc and tests.

## 0.0.6 (14/05/2015)

Add support for Json4s (both Native and Jackson implementations)

## 0.0.5 (13/05/2015)

We should be API ready. Just need more tests and scaladoc before production ready.


================================================
FILE: CONTRIBUTING.md
================================================
## Contributor Guide

For any bug, new feature, or documentation improvement,
the best way to start a conversation is by creating a new issue on github.

You're welcome to submit PRs right away without creating a ticket (issue) first, but be aware that there
is no guarantee your PR is going to be merged so your work might be for nothing.

## Tests

Continuous integration will run tests on your PR, needless to say it has to be green to be merged :).

To run the tests locally:

- Run all tests with `sbt testAll` (if `java.lang.LinkageError`, just re-run the command)
- Run a single project test, for example `sbt circeProject/test`

## Formatting

The project using [Scalafmt](https://scalameta.org/scalafmt/) for formatting.

Before submitting your PR you can format the code by running `sbt format`, but the best way is to configure your IDE/Editor
to pick up the Scalafmt config from the repo and format it automatically. It is supported at least by IntelliJ and VSCode.

## Documentation

To have a locally running doc website and test your documentation changes:

- `sbt ~docs/makeMicrosite`
- `cd docs/target/site`
- `jekyll serve -b /jwt-scala`
- Go to [http://localhost:4000/jwt-scala/](http://localhost:4000/jwt-scala/)

## Publishing

Create a release with a new tag on GitHub, and a new version with automatically be published to Sonatype by Github Actions with the corresponding version number.

Documentation (microsite + scaladoc) can be published with:

- `sbt publish-doc`


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS


================================================
FILE: README.md
================================================
# JWT Scala

Scala support for JSON Web Token ([JWT](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token)).
Supports Java 8+, Scala 2.12, Scala 2.13 and Scala 3 (for json libraries that support it).
Dependency free.
Optional helpers for Play Framework, Play JSON, Json4s Native, Json4s Jackson, Circe, uPickle and Argonaut.

[Contributor's guide](https://github.com/jwt-scala/jwt-scala/blob/main/CONTRIBUTING.md)

## Usage

Detailed documentation is on the [Microsite](https://jwt-scala.github.io/jwt-scala).

JWT Scala is divided in several sub-projects each targeting a specific JSON library,
check the doc from the menu of the Microsite for installation and usage instructions.

## Algorithms

If you are using `String` key, please keep in mind that such keys need to be parsed. Rather than implementing a super complex parser, the one in JWT Scala is pretty simple and might not work for all use-cases (especially for ECDSA keys). In such case, consider using `SecretKey` or `PrivateKey` or `PublicKey` directly. It is way better for you. All API support all those types.

Check [ECDSA samples](https://jwt-scala.github.io/jwt-scala/jwt-core-jwt-ecdsa.html) for more infos.

| Name  | Description                    |
| ----- | ------------------------------ |
| HMD5  | HMAC using MD5 algorithm       |
| HS224 | HMAC using SHA-224 algorithm   |
| HS256 | HMAC using SHA-256 algorithm   |
| HS384 | HMAC using SHA-384 algorithm   |
| HS512 | HMAC using SHA-512 algorithm   |
| RS256 | RSASSA using SHA-256 algorithm |
| RS384 | RSASSA using SHA-384 algorithm |
| RS512 | RSASSA using SHA-512 algorithm |
| ES256 | ECDSA using SHA-256 algorithm  |
| ES384 | ECDSA using SHA-384 algorithm  |
| ES512 | ECDSA using SHA-512 algorithm  |
| EdDSA | EdDSA signature algorithms     |

## Security concerns

This lib doesn't want to impose anything, that's why, by default, a JWT claim is totally empty. That said, you should always add an `issuedAt` attribute to it, probably using `claim.issuedNow`.
The reason is that even HTTPS isn't perfect and having always the same chunk of data transfered can be of a big help to crack it. Generating a slightly different token at each request is way better even if it adds a bit of payload to the response.
If you are using a session timeout through the `expiration` attribute which is extended at each request, that's fine too. I can't find the article I read about that vulnerability but if someone has some resources about the topic, I would be glad to link them.

## License

This software is licensed under the Apache 2 license, quoted below.

Copyright 2021 JWT-Scala Contributors.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


================================================
FILE: build.sbt
================================================
import scala.io.Source
import scala.sys.process._

import com.jsuereth.sbtpgp.PgpKeys._
import sbt.Keys._
import sbt.Tests._
import sbt._

val previousVersion = "9.4.0"
val buildVersion = "9.4.1"

val scala212 = "2.12.20"
val scala213 = "2.13.16"
val scala3 = "3.3.7"

Global / onChangedBuildSource := ReloadOnSourceChanges
ThisBuild / versionScheme := Some("early-semver")

val projects = Seq(
  "playJson",
  "json4sNative",
  "json4sJackson",
  "zioJson",
  "argonaut",
  "playFramework"
)
val crossProjects = Seq(
  "core",
  "circe",
  "upickle"
)
val allProjects = crossProjects.flatMap(p => Seq(s"${p}JVM", s"${p}JS", s"${p}Native")) ++ projects

addCommandAlias("publish-doc", "docs/makeMicrosite; docs/publishMicrosite")

addCommandAlias("testAll", allProjects.map(p => p + "/test").mkString(";", ";", ""))

addCommandAlias("format", "all scalafmtAll scalafmtSbt")

addCommandAlias("formatCheck", "all scalafmtCheckAll scalafmtSbtCheck")

lazy val cleanScript = taskKey[Unit]("Clean tmp files")
cleanScript := {
  "./scripts/clean.sh" !
}

lazy val docsMappingsAPIDir: SettingKey[String] =
  settingKey[String]("Name of subdirectory in site target directory for api docs")

val crossVersionAll = Seq(scala212, scala213, scala3)
val crossVersionNo212 = Seq(scala213, scala3)

val baseSettings = Seq(
  organization := "com.github.jwt-scala",
  ThisBuild / scalaVersion := scala213,
  crossScalaVersions := crossVersionAll,
  autoAPIMappings := true,
  libraryDependencies ++= Seq(Libs.munit.value, Libs.munitScalacheck.value),
  testFrameworks += new TestFramework("munit.Framework"),
  mimaFailOnNoPrevious := false,
  Test / aggregate := false,
  Test / fork := true,
  Test / parallelExecution := false,
  Compile / doc / scalacOptions ~= (_.filterNot(_ == "-Xfatal-warnings")),
  Compile / doc / scalacOptions ++= Seq(
    "-no-link-warnings" // Suppresses problems with Scaladoc @throws links
  ),
  scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match {
    case Some((2, _)) => Seq("-Xsource:3")
    case _            => Nil
  }),
  javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
)

val publishSettings = Seq(
  homepage := Some(url("https://jwt-scala.github.io/jwt-scala/")),
  apiURL := Some(url("https://jwt-scala.github.io/jwt-scala/api/")),
  Test / publishArtifact := false,
  licenses += ("Apache-2.0", url("http://www.apache.org/licenses/LICENSE-2.0")),
  pomIncludeRepository := { _ => false },
  scmInfo := Some(
    ScmInfo(
      url("https://github.com/jwt-scala/jwt-scala"),
      "scm:git@github.com:jwt-scala/jwt-scala.git"
    )
  ),
  developers := List(
    Developer(
      id = "pdi",
      name = "Paul Dijou",
      email = "paul.dijou@gmail.com",
      url = url("http://pauldijou.fr")
    ),
    Developer(
      id = "erwan",
      name = "Erwan Loisant",
      email = "erwan@loisant.com",
      url = url("https://caffeinelab.net")
    )
  ),
  publishConfiguration := publishConfiguration.value.withOverwrite(true),
  publishSignedConfiguration := publishSignedConfiguration.value.withOverwrite(true),
  publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true),
  publishLocalSignedConfiguration := publishLocalSignedConfiguration.value.withOverwrite(true)
)

val noPublishSettings = Seq(
  publish := (()),
  publishLocal := (()),
  publishArtifact := false
)

lazy val commonJsSettings = Seq(
  Test / fork := false
)

// Normal published settings
val releaseSettings = baseSettings ++ publishSettings

// Local non-published projects
val localSettings = baseSettings ++ noPublishSettings

lazy val jwtScala = project
  .in(file("."))
  .settings(localSettings)
  .settings(
    name := "jwt-scala"
  )
  .aggregate(
    json4sNative,
    json4sJackson,
    circe.jvm,
    circe.js,
    circe.native,
    upickle.jvm,
    upickle.js,
    upickle.native,
    zioJson,
    playFramework,
    argonaut
  )
  .dependsOn(
    json4sNative,
    json4sJackson,
    circe.jvm,
    circe.js,
    circe.native,
    upickle.jvm,
    upickle.js,
    upickle.native,
    zioJson,
    playFramework,
    argonaut
  )
  .settings(crossScalaVersions := List())

lazy val docs = project
  .in(file("docs"))
  .enablePlugins(
    SitePreviewPlugin,
    SiteScaladocPlugin,
    ScalaUnidocPlugin,
    ParadoxSitePlugin,
    ParadoxMaterialThemePlugin
  )
  .settings(name := "jwt-docs")
  .settings(localSettings)
  .settings(
    libraryDependencies ++= Seq("org.playframework" %% "play-test" % Versions.play),
    ScalaUnidoc / siteSubdirName := "api",
    addMappingsToSiteDir(
      ScalaUnidoc / packageDoc / mappings,
      ScalaUnidoc / siteSubdirName
    ),
    ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(
      core.jvm,
      circe.jvm,
      json4sNative,
      upickle.jvm,
      zioJson,
      playJson,
      playFramework,
      argonaut,
      zioJson
    ),
    baseSettings,
    publishArtifact := false,
    Compile / paradoxMaterialTheme ~= (_.withRepository(
      uri("https://github.com/jwt-scala/jwt-scala")
    )),
    packageSite / artifactPath := new java.io.File("target/artifact.zip")
  )
  .dependsOn(playFramework, json4sNative, circe.jvm, upickle.jvm, zioJson, argonaut)

lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
  .crossType(CrossType.Full)
  .settings(releaseSettings)
  .settings(name := "jwt-core", libraryDependencies ++= Seq(Libs.bouncyCastle))
  .jsSettings(commonJsSettings)
  .jsSettings(
    libraryDependencies ++= Seq(
      Libs.scalaJavaTime.value,
      Libs.scalajsSecureRandom.value
    )
  )
  .nativeSettings(
    libraryDependencies ++= Seq(
      Libs.scalaJavaTime.value
    ),
    Test / fork := false
  )

lazy val jsonCommon = crossProject(JSPlatform, JVMPlatform, NativePlatform)
  .crossType(CrossType.Pure)
  .in(file("json/common"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-json-common"
  )
  .jsSettings(commonJsSettings)
  .nativeSettings(Test / fork := false)
  .aggregate(core)
  .dependsOn(core % "compile->compile;test->test")

lazy val playJson = project
  .in(file("json/play-json"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-play-json",
    libraryDependencies ++= Seq(Libs.playJson)
  )
  .aggregate(jsonCommon.jvm)
  .dependsOn(jsonCommon.jvm % "compile->compile;test->test")

lazy val circe = crossProject(JSPlatform, JVMPlatform, NativePlatform)
  .crossType(CrossType.Full)
  .in(file("json/circe"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-circe",
    libraryDependencies ++= Seq(
      Libs.circeCore.value,
      Libs.circeJawn.value,
      Libs.circeParse.value,
      Libs.circeGeneric.value % "test"
    )
  )
  .jsSettings(commonJsSettings)
  .nativeSettings(Test / fork := false)
  .aggregate(jsonCommon)
  .dependsOn(jsonCommon % "compile->compile;test->test")

lazy val upickle = crossProject(JSPlatform, JVMPlatform, NativePlatform)
  .crossType(CrossType.Full)
  .in(file("json/upickle"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-upickle",
    libraryDependencies ++= Seq(Libs.upickle.value)
  )
  .jsSettings(commonJsSettings)
  .nativeSettings(Test / fork := false)
  .aggregate(jsonCommon)
  .dependsOn(jsonCommon % "compile->compile;test->test")

lazy val zioJson = project
  .in(file("json/zio-json"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-zio-json",
    libraryDependencies ++= Seq(Libs.zioJson)
  )
  .aggregate(jsonCommon.jvm)
  .dependsOn(jsonCommon.jvm % "compile->compile;test->test")

lazy val json4sCommon = project
  .in(file("json/json4s-common"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-json4s-common",
    libraryDependencies ++= Seq(Libs.json4sCore)
  )
  .aggregate(jsonCommon.jvm)
  .dependsOn(jsonCommon.jvm % "compile->compile;test->test")

lazy val json4sNative = project
  .in(file("json/json4s-native"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-json4s-native",
    libraryDependencies ++= Seq(Libs.json4sNative)
  )
  .aggregate(json4sCommon)
  .dependsOn(json4sCommon % "compile->compile;test->test")

lazy val json4sJackson = project
  .in(file("json/json4s-jackson"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-json4s-jackson",
    libraryDependencies ++= Seq(Libs.json4sJackson)
  )
  .aggregate(json4sCommon)
  .dependsOn(json4sCommon % "compile->compile;test->test")

lazy val argonaut = project
  .in(file("json/argonaut"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-argonaut",
    libraryDependencies ++= Seq(Libs.argonaut)
  )
  .aggregate(jsonCommon.jvm)
  .dependsOn(jsonCommon.jvm % "compile->compile;test->test")

def groupPlayTest(tests: Seq[TestDefinition], files: Seq[File]) = tests.map { t =>
  val options = ForkOptions()
  Group(t.name, Seq(t), SubProcess(options))
}

lazy val playFramework = project
  .in(file("play"))
  .settings(releaseSettings)
  .settings(
    name := "jwt-play",
    crossScalaVersions := crossVersionNo212,
    libraryDependencies ++= Seq(Libs.play, Libs.playTest, Libs.guice),
    Test / testGrouping := groupPlayTest(
      (Test / definedTests).value,
      (Test / dependencyClasspath).value.files
    )
  )
  .aggregate(playJson)
  .dependsOn(playJson % "compile->compile;test->test")


================================================
FILE: core/jvm/src/test/scala/JwtSpec.scala
================================================
package pdi.jwt

import scala.util.{Success, Try}

import pdi.jwt.algorithms.*
import pdi.jwt.exceptions.*

class JwtSpec extends munit.FunSuite with Fixture {
  val afterExpirationJwt: Jwt = Jwt(afterExpirationClock)
  val beforeNotBeforeJwt: Jwt = Jwt(beforeNotBeforeClock)
  val afterNotBeforeJwt: Jwt = Jwt(afterNotBeforeClock)
  val validTimeJwt: Jwt = Jwt(validTimeClock)

  def battleTestEncode(d: DataEntryBase, key: String, jwt: Jwt) = {
    assertEquals(d.tokenEmpty, jwt.encode(claim))
    assertEquals(d.token, jwt.encode(d.header, claim, key, d.algo))
    assertEquals(d.token, jwt.encode(claim, key, d.algo))
    assertEquals(d.tokenEmpty, jwt.encode(claimClass))
    assertEquals(d.token, jwt.encode(claimClass, key, d.algo))
    assertEquals(d.token, jwt.encode(d.headerClass, claimClass, key))
  }

  test("should parse JSON with spaces") {
    assert(Jwt.isValid(tokenWithSpaces))
  }

  test("should decode subject with a dash") {
    Jwt.decode(validTimeJwt.encode("""{"sub":"das-hed"""")) match {
      case Success(jwt) => assertEquals(jwt.subject, Option("das-hed"))
      case _            => fail("failed decoding token")
    }
  }

  test("should decode subject with an underscore") {
    Jwt.decode(validTimeJwt.encode("""{"sub":"das_hed"""")) match {
      case Success(jwt) => assertEquals(jwt.subject, Option("das_hed"))
      case _            => fail("failed decoding token")
    }
  }

  test("should decode jti with dashes") {
    val id = java.util.UUID.randomUUID().toString
    Jwt.decode(validTimeJwt.encode(s"""{"jti":"$id"""")) match {
      case Success(jwt) => assertEquals(jwt.jwtId, Option(id))
      case _            => fail("failed decoding token")
    }
  }

  test("should decode issuer with dashes") {
    Jwt.decode(validTimeJwt.encode(s"""{"iss":"das-_hed"""")) match {
      case Success(jwt) => assertEquals(jwt.issuer, Option("das-_hed"))
      case _            => fail("failed decoding token")
    }
  }
  test("should encode Hmac") {
    data.foreach { d => battleTestEncode(d, secretKey, validTimeJwt) }
  }

  test("should encode RSA") {
    dataRSA.foreach { d => battleTestEncode(d, privateKeyRSA, validTimeJwt) }
  }

  test("should encode EdDSA") {
    dataEdDSA.foreach { d => battleTestEncode(d, privateKeyEd25519, validTimeJwt) }
  }

  test("should be symmetric") {
    data.foreach { d =>
      testTryAll(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, secretKey, d.algo),
          secretKey,
          JwtAlgorithm.allHmac()
        ),
        (d.headerClass, claimClass, d.signature),
        d.algo.fullName
      )
    }
  }

  test("should be symmetric (RSA)") {
    dataRSA.foreach { d =>
      testTryAllWithoutSignature(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, randomRSAKey.getPrivate, d.algo),
          randomRSAKey.getPublic,
          JwtAlgorithm.allRSA()
        ),
        (d.headerClass, claimClass),
        d.algo.fullName
      )

      testTryAllWithoutSignature(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, randomRSAKey.getPrivate, d.algo),
          randomRSAKey.getPublic
        ),
        (d.headerClass, claimClass),
        d.algo.fullName
      )
    }
  }

  test("should be symmetric (ECDSA)") {
    dataECDSA.foreach { d =>
      testTryAllWithoutSignature(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, randomECKey.getPrivate, d.algo),
          randomECKey.getPublic,
          JwtAlgorithm.allECDSA()
        ),
        (d.headerClass, claimClass),
        d.algo.fullName
      )
    }

  }

  test("should be symmetric (EdDSA)") {
    dataEdDSA.foreach { d =>
      testTryAllWithoutSignature(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, randomEd25519Key.getPrivate, d.algo),
          randomEd25519Key.getPublic,
          JwtAlgorithm.allEdDSA()
        ),
        (d.headerClass, claimClass),
        d.algo.fullName
      )

      testTryAllWithoutSignature(
        validTimeJwt.decodeAll(
          validTimeJwt.encode(d.header, claim, randomEd25519Key.getPrivate, d.algo),
          randomEd25519Key.getPublic
        ),
        (d.headerClass, claimClass),
        d.algo.fullName
      )
    }
  }

  test("should decodeRawAll") {
    data.foreach { d =>
      assertEquals(
        validTimeJwt.decodeRawAll(d.token, secretKey, JwtAlgorithm.allHmac()),
        Success((d.header, claim, d.signature)),
        d.algo.fullName
      )
      assertEquals(
        validTimeJwt.decodeRawAll(d.token, secretKeyOf(d.algo)),
        Success((d.header, claim, d.signature)),
        d.algo.fullName
      )
      assertEquals(
        validTimeJwt.decodeRawAll(d.token, secretKeyOf(d.algo), JwtAlgorithm.allHmac()),
        Success((d.header, claim, d.signature)),
        d.algo.fullName
      )
    }
  }

  test("should decodeRaw") {
    data.foreach { d =>
      assertEquals(
        validTimeJwt.decodeRaw(d.token, secretKey, JwtAlgorithm.allHmac()),
        Success((claim)),
        d.algo.fullName
      )
      assertEquals(
        validTimeJwt.decodeRaw(d.token, secretKeyOf(d.algo)),
        Success((claim)),
        d.algo.fullName
      )
      assertEquals(
        validTimeJwt.decodeRaw(d.token, secretKeyOf(d.algo), JwtAlgorithm.allHmac()),
        Success((claim)),
        d.algo.fullName
      )
    }
  }

  test("should decodeAll") {
    data.foreach { d =>
      testTryAll(
        validTimeJwt.decodeAll(d.token, secretKey, JwtAlgorithm.allHmac()),
        (d.headerClass, claimClass, d.signature),
        d.algo.fullName
      )

      testTryAll(
        validTimeJwt.decodeAll(d.token, secretKeyOf(d.algo)),
        (d.headerClass, claimClass, d.signature),
        d.algo.fullName
      )
    }
  }

  test("should decode") {
    data.foreach { d =>
      assertEquals(
        validTimeJwt.decode(d.token, secretKey, JwtAlgorithm.allHmac()).get,
        claimClass,
        d.algo.fullName
      )

      assertEquals(
        validTimeJwt.decode(d.token, secretKeyOf(d.algo)).get,
        claimClass,
        d.algo.fullName
      )
    }
  }

  test("should validate correct tokens") {

    data.foreach { d =>
      assertEquals(
        (),
        validTimeJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac()),
        d.algo.fullName
      )
      assert(validTimeJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac()), d.algo.fullName)
      assertEquals((), validTimeJwt.validate(d.token, secretKeyOf(d.algo)), d.algo.fullName)
      assert(validTimeJwt.isValid(d.token, secretKeyOf(d.algo)), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      assertEquals(
        (),
        validTimeJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),
        d.algo.fullName
      )
      assert(validTimeJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA()), d.algo.fullName)
    }

    dataEdDSA.foreach { d =>
      assertEquals(
        (),
        validTimeJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),
        d.algo.fullName
      )
      assert(
        validTimeJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),
        d.algo.fullName
      )
    }
  }

  def oneLine(key: String) = key.replaceAll("\r\n", " ").replaceAll("\n", " ")

  test("should validate using RSA keys converted to single line") {
    val pubKey = oneLine(publicKeyRSA)
    dataRSA.foreach { d =>
      assertEquals(
        (),
        validTimeJwt.validate(d.token, pubKey, JwtAlgorithm.allRSA()),
        d.algo.fullName
      )
    }
  }

  test("should validate ECDSA from other implementations") {
    val publicKey =
      "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQASisgweVL1tAtIvfmpoqvdXF8sPKTV9YTKNxBwkdkm+/auh4pR8TbaIfsEzcsGUVv61DFNFXb0ozJfurQ59G2XcgAn3vROlSSnpbIvuhKrzL5jwWDTaYa5tVF1Zjwia/5HUhKBkcPuWGXg05nMjWhZfCuEetzMLoGcHmtvabugFrqsAg="
    val verifier = (token: String) => {
      assert(Jwt.isValid(token, publicKey, Seq(JwtAlgorithm.ES512)))
    }
    // Test verification for token created using https://github.com/auth0/node-jsonwebtoken/tree/v7.0.1
    verifier(
      "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0ZXN0IjoidGVzdCIsImlhdCI6MTQ2NzA2NTgyN30.Aab4x7HNRzetjgZ88AMGdYV2Ml7kzFbl8Ql2zXvBores7iRqm2nK6810ANpVo5okhHa82MQf2Q_Zn4tFyLDR9z4GAcKFdcAtopxq1h8X58qBWgNOc0Bn40SsgUc8wOX4rFohUCzEtnUREePsvc9EfXjjAH78WD2nq4tn-N94vf14SncQ"
    )
    // Test verification for token created using https://github.com/jwt/ruby-jwt/tree/v1.5.4
    verifier(
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJ0ZXN0IjoidGVzdCJ9.AV26tERbSEwcoDGshneZmhokg-tAKUk0uQBoHBohveEd51D5f6EIs6cskkgwtfzs4qAGfx2rYxqQXr7LTXCNquKiAJNkTIKVddbPfped3_TQtmHZTmMNiqmWjiFj7Y9eTPMMRRu26w4gD1a8EQcBF-7UGgeH4L_1CwHJWAXGbtu7uMUn"
    )
  }

  test("should invalidate WTF tokens") {
    val tokens = Seq("1", "abcde", "", "a.b.c.d")

    tokens.foreach { token =>
      assert(Try(Jwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)
      assert(!Jwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), token)
    }
  }

  test("should invalidate non-base64 tokens") {
    val tokens = Seq("a.b", "a.b.c", "1.2.3", "abcde.azer.azer", "aze$.azer.azer")

    tokens.foreach { token =>
      assert(Try(Jwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)
      assert(!Jwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), token)
    }
  }

  test("should invalidate expired tokens") {
    data.foreach { d =>
      assert(Try(afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac())).isFailure)
      assert(
        !afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac()),
        d.algo.fullName
      )
      assert(Try(afterExpirationJwt.validate(d.token, secretKeyOf(d.algo))).isFailure)
      assert(!afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo)), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      assert(
        Try(afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure
      )
      assert(
        !afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA()),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      assert(
        Try(
          afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA())
        ).isFailure
      )
      assert(
        !afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),
        d.algo.fullName
      )
    }
  }

  test("should validate expired tokens with leeway") {
    val options = JwtOptions(leeway = 60)

    data.foreach { d =>
      afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac(), options)
      assert(
        afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac(), options),
        d.algo.fullName
      )
      afterExpirationJwt.validate(d.token, secretKeyOf(d.algo), options)
      assert(afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo), options), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)
      assert(
        afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)
      assert(
        afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),
        d.algo.fullName
      )
    }
  }

  test("should invalidate early tokens") {
    data.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)

      assert(Try(beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac())).isFailure)
      assert(!beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac()), d.algo.fullName)
      assert(Try(beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo))).isFailure)
      assert(!beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo)), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)

      assert(Try(beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure)
      assert(
        !beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA()),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)

      assert(
        Try(beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA())).isFailure
      )
      assert(
        !beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),
        d.algo.fullName
      )
    }
  }

  test("should validate early tokens with leeway") {
    val options = JwtOptions(leeway = 60)

    data.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)

      beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac(), options)
      assert(
        beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac(), options),
        d.algo.fullName
      )
      beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo), options)
      assert(beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo), options), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)

      assert(Try(beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA())).isFailure)
      assert(
        !beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA()),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)

      assert(
        Try(beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA())).isFailure
      )
      assert(
        !beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA()),
        d.algo.fullName
      )
    }
  }

  test("should invalidate wrong keys") {
    data.foreach { d =>
      assert(
        Try(
          validTimeJwt.validate(d.token, "wrong key", JwtAlgorithm.allHmac())
        ).isFailure
      )
      assert(!validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allHmac()), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      assert(!validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allRSA()), d.algo.fullName)
    }

    dataEdDSA.foreach { d =>
      assert(!validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allEdDSA()), d.algo.fullName)
    }
  }

  test("should fail on non-exposed algorithms") {
    data.foreach { d =>
      assert(
        Try(
          validTimeJwt.validate(d.token, secretKey, Seq.empty[JwtHmacAlgorithm])
        ).isFailure
      )
      assert(
        !validTimeJwt.isValid(d.token, secretKey, Seq.empty[JwtHmacAlgorithm]),
        d.algo.fullName
      )
    }

    data.foreach { d =>
      assert(Try(validTimeJwt.validate(d.token, secretKey, JwtAlgorithm.allRSA())).isFailure)
      assert(!validTimeJwt.isValid(d.token, secretKey, JwtAlgorithm.allRSA()), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      assert(
        Try(
          validTimeJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allHmac())
        ).isFailure
      )
      assert(!validTimeJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allHmac()), d.algo.fullName)
    }
  }

  test("should invalidate wrong algos") {
    val token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJXVEYifQ.e30"
    assert(Jwt.decode(token).isFailure)
    intercept[JwtNonSupportedAlgorithm] { Jwt.decode(token).get }
  }

  test("should decode tokens with unknown algos depending on options") {
    val token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJXVEYifQ.e30"
    val decoded = Jwt.decode(token, options = JwtOptions(signature = false))
    assert(decoded.isSuccess)
  }

  test("should skip expiration validation depending on options") {
    val options = JwtOptions(expiration = false)

    data.foreach { d =>
      afterExpirationJwt.validate(d.token, secretKey, JwtAlgorithm.allHmac(), options)
      assert(
        afterExpirationJwt.isValid(d.token, secretKey, JwtAlgorithm.allHmac(), options),
        d.algo.fullName
      )
      afterExpirationJwt.validate(d.token, secretKeyOf(d.algo), options)
      assert(afterExpirationJwt.isValid(d.token, secretKeyOf(d.algo), options), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      afterExpirationJwt.validate(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options)
      assert(
        afterExpirationJwt.isValid(d.token, publicKeyRSA, JwtAlgorithm.allRSA(), options),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      afterExpirationJwt.validate(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)
      assert(
        afterExpirationJwt.isValid(d.token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),
        d.algo.fullName
      )
    }
  }

  test("should skip notBefore validation depending on options") {
    val options = JwtOptions(notBefore = false)

    data.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, secretKey, d.algo)

      beforeNotBeforeJwt.validate(token, secretKey, JwtAlgorithm.allHmac(), options)
      assert(
        beforeNotBeforeJwt.isValid(token, secretKey, JwtAlgorithm.allHmac(), options),
        d.algo.fullName
      )
      beforeNotBeforeJwt.validate(token, secretKeyOf(d.algo), options)
      assert(beforeNotBeforeJwt.isValid(token, secretKeyOf(d.algo), options), d.algo.fullName)
    }

    dataRSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyRSA, d.algo)

      beforeNotBeforeJwt.validate(token, publicKeyRSA, JwtAlgorithm.allRSA(), options)
      assert(
        beforeNotBeforeJwt.isValid(token, publicKeyRSA, JwtAlgorithm.allRSA(), options),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      val claimNotBefore = claimClass.startsAt(notBefore)
      val token = beforeNotBeforeJwt.encode(claimNotBefore, privateKeyEd25519, d.algo)

      beforeNotBeforeJwt.validate(token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options)
      assert(
        beforeNotBeforeJwt.isValid(token, publicKeyEd25519, JwtAlgorithm.allEdDSA(), options),
        d.algo.fullName
      )
    }
  }

  test("should skip signature validation depending on options") {
    val options = JwtOptions(signature = false)

    data.foreach { d =>
      validTimeJwt.validate(d.token, "wrong key", JwtAlgorithm.allHmac(), options)
      assert(
        validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allHmac(), options),
        d.algo.fullName
      )
    }

    dataRSA.foreach { d =>
      assert(
        validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allRSA(), options),
        d.algo.fullName
      )
    }

    dataEdDSA.foreach { d =>
      assert(
        validTimeJwt.isValid(d.token, "wrong key", JwtAlgorithm.allEdDSA(), options),
        d.algo.fullName
      )
    }
  }

  def testTryAll(
      t: Try[(JwtHeader, JwtClaim, String)],
      exp: (JwtHeader, JwtClaim, String),
      clue: String
  ) = {
    assert(t.isSuccess, clue)
    val (h1, c1, s1) = t.get
    val (h2, c2, s2) = exp
    assertEquals(h1, h2)
    assertEquals(c1, c2)
    assertEquals(s1, s2)
  }

  def testTryAllWithoutSignature(
      t: Try[(JwtHeader, JwtClaim, String)],
      exp: (JwtHeader, JwtClaim, String),
      clue: String
  ) = {
    assert(t.isSuccess, clue)
    val (h1, c1, _) = t.get
    val (h2, c2, _) = exp
    assertEquals(h1, h2)
    assertEquals(c1, c2)
  }

  def testTryAllWithoutSignature(
      t: Try[(JwtHeader, JwtClaim, String)],
      exp: (JwtHeader, JwtClaim),
      clue: String
  ) = {
    assert(t.isSuccess, clue)
    val (h1, c1, _) = t.get
    val (h2, c2) = exp
    assertEquals(h1, h2)
    assertEquals(c1, c2)
  }

}


================================================
FILE: core/jvm/src/test/scala/JwtUtilsSpec.scala
================================================
package pdi.jwt

import java.security.spec.ECGenParameterSpec
import java.security.{KeyPairGenerator, SecureRandom}

import org.scalacheck.Gen
import org.scalacheck.Prop.*
import pdi.jwt.exceptions.JwtSignatureFormatException

case class TestObject(value: String) {
  override def toString(): String = this.value
}

class JwtUtilsSpec extends munit.ScalaCheckSuite with Fixture {
  val ENCODING = JwtUtils.ENCODING

  test("hashToJson should transform a seq of tuples to a valid JSON") {
    val values: Seq[(String, Seq[(String, Any)])] = Seq(
      """{"a":"b","c":1,"d":true,"e":2,"f":3.4,"g":5.6}""" -> Seq(
        "a" -> "b",
        "c" -> 1,
        "d" -> true,
        "e" -> 2L,
        "f" -> 3.4f,
        "g" -> 5.6
      ),
      "{}" -> Seq(),
      """{"a\"b":"a\"b","c\"d":"c\"d","e\"f":["e\"f","e\"f"]}""" -> Seq(
        """a"b""" -> """a"b""",
        """c"d""" -> TestObject("""c"d"""),
        """e"f""" -> Seq("""e"f""", TestObject("""e"f"""))
      )
    )

    values.zipWithIndex.foreach { case (value, index) =>
      assertEquals(value._1, JwtUtils.hashToJson(value._2), "at index " + index)
    }
  }

  test("mergeJson should correctly merge 2 JSONs") {
    val values: Seq[(String, String, Seq[String])] = Seq(
      ("{}", "{}", Seq("{}")),
      ("""{"a":1}""", """{"a":1}""", Seq("")),
      ("""{"a":1}""", """{"a":1}""", Seq("{}")),
      ("""{"a":1}""", """{}""", Seq("""{"a":1}""")),
      ("""{"a":1}""", "", Seq("""{"a":1}""")),
      ("""{"a":1,"b":2}""", """{"a":1}""", Seq("""{"b":2}""")),
      ("""{"a":1,"b":2,"c":"d"}""", """{"a":1}""", Seq("""{"b":2}""", """{"c":"d"}"""))
    )

    values.zipWithIndex.foreach { case (value, index) =>
      assertEquals(value._1, JwtUtils.mergeJson(value._2, value._3: _*), "at index " + index)
    }
  }

  test("Claim.toJson should correctly encode a Claim to JSON") {
    val claim = JwtClaim(
      issuer = Some(""),
      audience = Some(Set("")),
      subject = Some("da1b3852-6827-11e9-a923-1681be663d3e"),
      expiration = Some(1597914901),
      issuedAt = Some(1566378901),
      content = "{\"a\":\"da1b3852-6827-11e9-a923-1681be663d3e\",\"b\":123.34}"
    )

    val jsonClaim =
      """{"iss":"","sub":"da1b3852-6827-11e9-a923-1681be663d3e","aud":"","exp":1597914901,"iat":1566378901,"a":"da1b3852-6827-11e9-a923-1681be663d3e","b":123.34}"""

    assertEquals(jsonClaim, claim.toJson)
  }

  test("transcodeSignatureToDER should throw JwtValidationException if signature is too long") {
    val signature = JwtUtils.bytify(
      "AU6-jw28DX1QMY0Ar8CTcnIAc0WKGe3nNVHkE7ayHSxvOLxE5YQSiZtbPn3y-vDHoQCOMId4rPdIJhD_NOUqnH_rAKA5w9ZlhtW0GwgpvOg1_5oLWnWXQvPjJjC5YsLqEssoMITtOmfkBsQMgLAF_LElaaCWhkJkOCtcZmroUW_b5CXB"
    )
    interceptMessage[JwtSignatureFormatException]("Invalid ECDSA signature format") {
      JwtUtils.transcodeSignatureToDER(signature ++ signature)
    }
  }

  test("transcodeSignatureToDER should transocde empty signature") {
    val signature: Array[Byte] = Array[Byte](0)
    JwtUtils.transcodeSignatureToDER(signature)
  }

  test("transcodeSignatureToConcat should throw JwtValidationException if length incorrect") {
    val signature = JwtUtils.bytify(
      "MIEAAGg3OVb/ZeX12cYrhK3c07TsMKo7Kc6SiqW++4CAZWCX72DkZPGTdCv2duqlupsnZL53hiG3rfdOLj8drndCU+KHGrn5EotCATdMSLCXJSMMJoHMM/ZPG+QOHHPlOWnAvpC1v4lJb32WxMFNz1VAIWrl9Aa6RPG1GcjCTScKjvEE"
    )
    interceptMessage[JwtSignatureFormatException]("Invalid ECDSA signature format") {
      JwtUtils.transcodeSignatureToConcat(signature, 132)
    }
  }

  test(
    "transcodeSignatureToConcat should throw JwtValidationException if signature is incorrect "
  ) {
    val signature = JwtUtils.bytify(
      "MIGBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    )
    interceptMessage[JwtSignatureFormatException]("Invalid ECDSA signature format") {
      JwtUtils.transcodeSignatureToConcat(signature, 132)
    }
  }

  test(
    "transcodeSignatureToConcat should throw JwtValidationException if signature is incorrect 2"
  ) {
    val signature = JwtUtils.bytify(
      "MIGBAD4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
    )
    interceptMessage[JwtSignatureFormatException]("Invalid ECDSA signature format") {
      JwtUtils.transcodeSignatureToConcat(signature, 132)
    }
  }

  test("transcodeSignatureToConcat and transcodeSignatureToDER should be symmetric") {
    val signature = JwtUtils.bytify(
      "AbxLPbA3dm9V0jt6c_ahf8PYioFvnryTe3odgolhcgwBUl4ifpwUBJ--GgiXC8vms45c8vI40ZSdkm5NoNn1wTHOAfkepNy-RRKHmBzAoWrWmBIb76yPa0lsjdAPEAXcbGfaQV8pKq7W10dpB2B-KeJxVonMuCLJHPuqsUl9S7CfASu2"
    )
    val dER: Array[Byte] = JwtUtils.transcodeSignatureToDER(signature)
    val result = JwtUtils.transcodeSignatureToConcat(
      dER,
      JwtUtils.getSignatureByteArrayLength(JwtAlgorithm.ES512)
    )
    assertArrayEquals(signature, result)
  }

  test(
    "transcodeSignatureToConcat and transcodeSignatureToDER should be symmetric for generated tokens"
  ) {
    val ecGenSpec = new ECGenParameterSpec(ecCurveName)
    val generatorEC = KeyPairGenerator.getInstance(JwtUtils.ECDSA)
    generatorEC.initialize(ecGenSpec, new SecureRandom())
    val randomECKey = generatorEC.generateKeyPair()
    val header = """{"typ":"JWT","alg":"ES512"}"""
    val claim = """{"test":"t"}"""

    val signature = Jwt(validTimeClock)
      .encode(header, claim, randomECKey.getPrivate, JwtAlgorithm.ES512)
      .split("\\.")(2)
    assertEquals(
      signature,
      JwtUtils.stringify(
        JwtUtils.transcodeSignatureToConcat(
          JwtUtils.transcodeSignatureToDER(JwtUtils.bytify(signature)),
          JwtUtils.getSignatureByteArrayLength(JwtAlgorithm.ES512)
        )
      )
    )
  }

  test("splitString should do nothing") {
    forAll(Gen.asciiStr.suchThat(s => s.nonEmpty && !s.contains('a'))) { (value: String) =>
      assertArrayEquals(
        JwtUtils.splitString(value, 'a'),
        Array(value)
      )
    }
  }

  test("splitString should split once") {
    assertArrayEquals(JwtUtils.splitString("qwertyAzxcvb", 'A'), Array("qwerty", "zxcvb"))
  }

  test("splitString should split a token") {
    assertArrayEquals(
      JwtUtils.splitString("header.claim.signature", '.'),
      Array("header", "claim", "signature")
    )
  }

  test("splitString should split a token without signature") {
    assertArrayEquals(JwtUtils.splitString("header.claim", '.'), Array("header", "claim"))
  }

  test("splitString should split a token with an empty signature") {
    assertArrayEquals(JwtUtils.splitString("header.claim.", '.'), Array("header", "claim"))
  }

  test("splitString should split a token with an empty header") {
    assertArrayEquals(JwtUtils.splitString(".claim.", '.'), Array("", "claim"))
  }

  test("splitString should be the same as normal split") {
    var token = "header.claim.signature"
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = "header.claim."
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = "header.claim"
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = ".claim.signature"
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = ".claim."
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = "1"
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
    token = "a.b.c.d"
    assertArrayEquals(token.split("\\."), JwtUtils.splitString(token, '.'))
  }

  private def assertArrayEquals[A](arr1: Array[A], arr2: Array[A]): Unit = {
    assertEquals(arr1.toSeq, arr2.toSeq)
  }
}


================================================
FILE: core/shared/src/main/scala/Jwt.scala
================================================
package pdi.jwt

import java.time.Clock
import scala.util.matching.Regex

/** Test implementation of [[JwtCore]] using only Strings. Most of the time, you should use a lib
  * implementing JSON and shouldn't be using this object. But just in case you need pure Scala
  * support, here it is.
  *
  * To see a full list of samples, check the
  * [[https://jwt-scala.github.io/jwt-scala/jwt-core-jwt.html online documentation]].
  *
  * '''Warning''': since there is no JSON support in Scala, this object doesn't have any way to
  * parse a JSON string as an AST, so it only uses regex with all the limitations it implies. Try
  * not to use keys like `exp` and `nbf` in sub-objects of the claim. For example, if you try to use
  * the following claim: `{"user":{"exp":1},"exp":1300819380}`, it should be correct but it will
  * fail because the regex extracting the expiration will return `1` instead of `1300819380`. Sorry
  * about that.
  */
object Jwt extends Jwt(Clock.systemUTC) {
  def apply(clock: Clock) = new Jwt(clock)
}

class Jwt(override val clock: Clock) extends JwtCore[JwtHeader, JwtClaim] {
  private val extractAlgorithmRegex = "\"alg\" *: *\"([a-zA-Z0-9]+)\"".r
  protected def extractAlgorithm(header: String): Option[JwtAlgorithm] =
    (extractAlgorithmRegex.findFirstMatchIn(header)).map(_.group(1)).flatMap {
      case "none"       => None
      case name: String => Some(JwtAlgorithm.fromString(name))
    }

  private val extractIssuerRegex = "\"iss\" *: *\"([\\-a-zA-Z0-9_]*)\"".r
  protected def extractIssuer(claim: String): Option[String] =
    (extractIssuerRegex.findFirstMatchIn(claim)).map(_.group(1))

  private val extractSubjectRegex = "\"sub\" *: *\"([\\-a-zA-Z0-9_]*)\"".r
  protected def extractSubject(claim: String): Option[String] =
    (extractSubjectRegex.findFirstMatchIn(claim)).map(_.group(1))

  private val extractExpirationRegex = "\"exp\" *: *([0-9]+)".r
  protected def extractExpiration(claim: String): Option[Long] =
    (extractExpirationRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)

  private val extractNotBeforeRegex = "\"nbf\" *: *([0-9]+)".r
  protected def extractNotBefore(claim: String): Option[Long] =
    (extractNotBeforeRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)

  private val extractIssuedAtRegex = "\"iat\" *: *([0-9]+)".r
  protected def extractIssuedAt(claim: String): Option[Long] =
    (extractIssuedAtRegex.findFirstMatchIn(claim)).map(_.group(1)).map(_.toLong)

  private val extractJwtIdRegex = "\"jti\" *: *\"([\\-a-zA-Z0-9_]*)\"".r
  protected def extractJwtId(claim: String): Option[String] =
    (extractJwtIdRegex.findFirstMatchIn(claim)).map(_.group(1))

  private val clearStartRegex = "\\{ *,".r
  protected def clearStart(json: String): String =
    clearStartRegex.replaceFirstIn(json, "{")

  private val clearMiddleRegex = ", *(?=,)".r
  protected def clearMiddle(json: String): String =
    clearMiddleRegex.replaceAllIn(json, "")

  private val clearEndRegex = ", *\\}".r
  protected def clearEnd(json: String): String =
    clearEndRegex.replaceFirstIn(json, "}")

  protected def clearRegex(json: String, regex: Regex): String =
    regex.replaceFirstIn(json, "")

  protected def clearAll(json: String): String = {
    val dirtyJson = List(
      extractIssuerRegex,
      extractSubjectRegex,
      extractExpirationRegex,
      extractNotBeforeRegex,
      extractIssuedAtRegex,
      extractJwtIdRegex
    ).foldLeft(json)(clearRegex)

    clearStart(clearEnd(clearMiddle(dirtyJson)))
  }

  protected def headerToJson(header: JwtHeader): String = header.toJson
  protected def claimToJson(claim: JwtClaim): String = claim.toJson

  protected def extractAlgorithm(header: JwtHeader): Option[JwtAlgorithm] = header.algorithm
  protected def extractExpiration(claim: JwtClaim): Option[Long] = claim.expiration
  protected def extractNotBefore(claim: JwtClaim): Option[Long] = claim.notBefore

  protected def parseHeader(header: String): JwtHeader = JwtHeader(extractAlgorithm(header))

  protected def parseClaim(claim: String): JwtClaim =
    JwtClaim(
      content = clearAll(claim),
      issuer = extractIssuer(claim),
      subject = extractSubject(claim),
      expiration = extractExpiration(claim),
      notBefore = extractNotBefore(claim),
      issuedAt = extractIssuedAt(claim),
      jwtId = extractJwtId(claim)
    )
}


================================================
FILE: core/shared/src/main/scala/JwtAlgorithm.scala
================================================
package pdi.jwt

import scala.annotation.nowarn

import pdi.jwt.algorithms.JwtUnknownAlgorithm

sealed trait JwtAlgorithm {
  def name: String
  def fullName: String
}

package algorithms {
  sealed trait JwtAsymmetricAlgorithm extends JwtAlgorithm

  sealed trait JwtHmacAlgorithm extends JwtAlgorithm
  sealed trait JwtRSAAlgorithm extends JwtAsymmetricAlgorithm
  sealed trait JwtECDSAAlgorithm extends JwtAsymmetricAlgorithm
  sealed trait JwtEdDSAAlgorithm extends JwtAsymmetricAlgorithm
  final case class JwtUnknownAlgorithm(name: String) extends JwtAlgorithm {
    def fullName: String = name
  }
}

object JwtAlgorithm {

  /** Deserialize an algorithm from its string equivalent. Only real algorithms supported, if you
    * need to support "none", use "optionFromString".
    *
    * @return
    *   the actual instance of the algorithm
    * @param algo
    *   the name of the algorithm (e.g. HS256 or HmacSHA256)
    * @throws JwtNonSupportedAlgorithm
    *   in case the string doesn't match any known algorithm
    */
  @nowarn("cat=deprecation")
  def fromString(algo: String): JwtAlgorithm = algo match {
    case "HMD5"    => HMD5
    case "HS224"   => HS224
    case "HS256"   => HS256
    case "HS384"   => HS384
    case "HS512"   => HS512
    case "RS256"   => RS256
    case "RS384"   => RS384
    case "RS512"   => RS512
    case "ES256"   => ES256
    case "ES384"   => ES384
    case "ES512"   => ES512
    case "EdDSA"   => EdDSA
    case "Ed25519" => Ed25519
    case other     => JwtUnknownAlgorithm(other)
    // Missing PS256 PS384 PS512
  }

  /** Deserialize an algorithm from its string equivalent. If it's the special "none" algorithm,
    * return None, else, return Some with the corresponding algorithm inside.
    *
    * @return
    *   the actual instance of the algorithm
    * @param algo
    *   the name of the algorithm (e.g. none, HS256 or HmacSHA256)
    */
  def optionFromString(algo: String): Option[JwtAlgorithm] =
    Option(algo).filterNot(_ == "none").map(fromString)

  def allHmac(): Seq[algorithms.JwtHmacAlgorithm] = Seq(HMD5, HS224, HS256, HS384, HS512)

  @nowarn("cat=deprecation")
  def allAsymmetric(): Seq[algorithms.JwtAsymmetricAlgorithm] =
    Seq(RS256, RS384, RS512, ES256, ES384, ES512, EdDSA, Ed25519)

  def allRSA(): Seq[algorithms.JwtRSAAlgorithm] = Seq(RS256, RS384, RS512)

  def allECDSA(): Seq[algorithms.JwtECDSAAlgorithm] = Seq(ES256, ES384, ES512)

  @nowarn("cat=deprecation")
  def allEdDSA(): Seq[algorithms.JwtEdDSAAlgorithm] = Seq(EdDSA, Ed25519)

  case object HMD5 extends algorithms.JwtHmacAlgorithm {
    def name = "HMD5"; def fullName = "HmacMD5"
  }
  case object HS224 extends algorithms.JwtHmacAlgorithm {
    def name = "HS224"; def fullName = "HmacSHA224"
  }
  case object HS256 extends algorithms.JwtHmacAlgorithm {
    def name = "HS256"; def fullName = "HmacSHA256"
  }
  case object HS384 extends algorithms.JwtHmacAlgorithm {
    def name = "HS384"; def fullName = "HmacSHA384"
  }
  case object HS512 extends algorithms.JwtHmacAlgorithm {
    def name = "HS512"; def fullName = "HmacSHA512"
  }
  case object RS256 extends algorithms.JwtRSAAlgorithm {
    def name = "RS256"; def fullName = "SHA256withRSA"
  }
  case object RS384 extends algorithms.JwtRSAAlgorithm {
    def name = "RS384"; def fullName = "SHA384withRSA"
  }
  case object RS512 extends algorithms.JwtRSAAlgorithm {
    def name = "RS512"; def fullName = "SHA512withRSA"
  }
  case object ES256 extends algorithms.JwtECDSAAlgorithm {
    def name = "ES256"; def fullName = "SHA256withECDSA"
  }
  case object ES384 extends algorithms.JwtECDSAAlgorithm {
    def name = "ES384"; def fullName = "SHA384withECDSA"
  }
  case object ES512 extends algorithms.JwtECDSAAlgorithm {
    def name = "ES512"; def fullName = "SHA512withECDSA"
  }
  case object EdDSA extends algorithms.JwtEdDSAAlgorithm {
    def name = "EdDSA"; def fullName = "EdDSA"
  }
  @deprecated("Ed25519 is not a standard Json Web Algorithm name. Use EdDSA instead.", "9.3.0")
  case object Ed25519 extends algorithms.JwtEdDSAAlgorithm {
    def name = "Ed25519"; def fullName = "Ed25519"
  }
}


================================================
FILE: core/shared/src/main/scala/JwtArrayUtils.scala
================================================
package pdi.jwt

object JwtArrayUtils {

  /** A constant time equals comparison - does not terminate early if test will fail. For best
    * results always pass the expected value as the first parameter.
    *
    * Ported from BouncyCastle to remove the need for a runtime dependency.
    * https://github.com/bcgit/bc-java/blob/290df7b4edfc77b32d55d0a329bf15ef5b98733b/core/src/main/java/org/bouncycastle/util/Arrays.java#L136-L172
    *
    * @param expected
    *   first array
    * @param supplied
    *   second array
    * @return
    *   true if arrays equal, false otherwise.
    */
  def constantTimeAreEqual(expected: Array[Byte], supplied: Array[Byte]): Boolean =
    if (expected == supplied) true
    else if (expected == null || supplied == null) false
    else if (expected.length != supplied.length)
      !JwtArrayUtils.constantTimeAreEqual(expected, expected)
    else {
      var nonEqual = 0
      (0 until expected.length).foreach(i => nonEqual |= (expected(i) ^ supplied(i)))
      nonEqual == 0
    }
}


================================================
FILE: core/shared/src/main/scala/JwtBase64.scala
================================================
package pdi.jwt

object JwtBase64 {
  private lazy val encoder = java.util.Base64.getUrlEncoder()
  private lazy val decoder = java.util.Base64.getUrlDecoder()
  private lazy val decoderNonSafe = java.util.Base64.getDecoder()

  def encode(value: Array[Byte]): Array[Byte] = encoder.encode(value)
  def decode(value: Array[Byte]): Array[Byte] = decoder.decode(value)

  def encode(value: String): Array[Byte] = encode(JwtUtils.bytify(value))
  def decode(value: String): Array[Byte] = decoder.decode(value)

  // Since the complement character "=" is optional,
  // we can remove it to save some bits in the HTTP header
  def encodeString(value: Array[Byte]): String = encoder.encodeToString(value).replaceAll("=", "")
  def decodeString(value: Array[Byte]): String = JwtUtils.stringify(decode(value))

  def encodeString(value: String): String = encodeString(JwtUtils.bytify(value))
  def decodeString(value: String): String = decodeString(JwtUtils.bytify(value))

  def decodeNonSafe(value: Array[Byte]): Array[Byte] = decoderNonSafe.decode(value)
  def decodeNonSafe(value: String): Array[Byte] = decoderNonSafe.decode(value)
}


================================================
FILE: core/shared/src/main/scala/JwtClaim.scala
================================================
package pdi.jwt

import java.time.Clock

object JwtClaim {
  def apply(
      content: String = "{}",
      issuer: Option[String] = None,
      subject: Option[String] = None,
      audience: Option[Set[String]] = None,
      expiration: Option[Long] = None,
      notBefore: Option[Long] = None,
      issuedAt: Option[Long] = None,
      jwtId: Option[String] = None
  ) = new JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)
}

class JwtClaim(
    val content: String,
    val issuer: Option[String],
    val subject: Option[String],
    val audience: Option[Set[String]],
    val expiration: Option[Long],
    val notBefore: Option[Long],
    val issuedAt: Option[Long],
    val jwtId: Option[String]
) {

  def toJson: String = JwtUtils.mergeJson(
    JwtUtils.hashToJson(
      Seq(
        "iss" -> issuer,
        "sub" -> subject,
        "aud" -> audience.map(set => if (set.size == 1) set.head else set),
        "exp" -> expiration,
        "nbf" -> notBefore,
        "iat" -> issuedAt,
        "jti" -> jwtId
      ).collect { case (key, Some(value)) =>
        key -> value
      }
    ),
    content
  )

  def +(json: String): JwtClaim = {
    JwtClaim(
      JwtUtils.mergeJson(this.content, json),
      issuer,
      subject,
      audience,
      expiration,
      notBefore,
      issuedAt,
      jwtId
    )
  }

  def +(key: String, value: Any): JwtClaim = {
    JwtClaim(
      JwtUtils.mergeJson(this.content, JwtUtils.hashToJson(Seq(key -> value))),
      issuer,
      subject,
      audience,
      expiration,
      notBefore,
      issuedAt,
      jwtId
    )
  }

  // Ok, it's Any, but just use "primitive" types
  // It will not work with classes or case classes since, you know,
  // there is no way to serialize them to JSON out of the box.
  def ++[T <: Any](fields: (String, T)*): JwtClaim = {
    JwtClaim(
      JwtUtils.mergeJson(this.content, JwtUtils.hashToJson(fields)),
      issuer,
      subject,
      audience,
      expiration,
      notBefore,
      issuedAt,
      jwtId
    )
  }

  def by(issuer: String): JwtClaim = {
    JwtClaim(content, Option(issuer), subject, audience, expiration, notBefore, issuedAt, jwtId)
  }

  // content should be a valid stringified JSON
  def withContent(content: String): JwtClaim = {
    JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)
  }

  def to(audience: String): JwtClaim = {
    JwtClaim(
      content,
      issuer,
      subject,
      Option(Set(audience)),
      expiration,
      notBefore,
      issuedAt,
      jwtId
    )
  }

  def to(audience: Set[String]): JwtClaim = {
    JwtClaim(content, issuer, subject, Option(audience), expiration, notBefore, issuedAt, jwtId)
  }

  def about(subject: String): JwtClaim = {
    JwtClaim(content, issuer, Option(subject), audience, expiration, notBefore, issuedAt, jwtId)
  }

  def withId(id: String): JwtClaim = {
    JwtClaim(content, issuer, subject, audience, expiration, notBefore, issuedAt, Option(id))
  }

  def expiresAt(seconds: Long): JwtClaim =
    JwtClaim(content, issuer, subject, audience, Option(seconds), notBefore, issuedAt, jwtId)

  def expiresIn(seconds: Long)(implicit clock: Clock): JwtClaim = expiresAt(
    JwtTime.nowSeconds + seconds
  )

  def expiresNow(implicit clock: Clock): JwtClaim = expiresAt(JwtTime.nowSeconds)

  def startsAt(seconds: Long): JwtClaim =
    JwtClaim(content, issuer, subject, audience, expiration, Option(seconds), issuedAt, jwtId)

  def startsIn(seconds: Long)(implicit clock: Clock): JwtClaim = startsAt(
    JwtTime.nowSeconds + seconds
  )

  def startsNow(implicit clock: Clock): JwtClaim = startsAt(JwtTime.nowSeconds)

  def issuedAt(seconds: Long): JwtClaim =
    JwtClaim(content, issuer, subject, audience, expiration, notBefore, Option(seconds), jwtId)

  def issuedIn(seconds: Long)(implicit clock: Clock): JwtClaim = issuedAt(
    JwtTime.nowSeconds + seconds
  )

  def issuedNow(implicit clock: Clock): JwtClaim = issuedAt(JwtTime.nowSeconds)

  def isValid(issuer: String, audience: String)(implicit clock: Clock): Boolean =
    this.audience.exists(_ contains audience) && this.isValid(issuer)

  def isValid(issuer: String)(implicit clock: Clock): Boolean =
    this.issuer.contains(issuer) && this.isValid

  def isValid(implicit clock: Clock): Boolean =
    JwtTime.nowIsBetweenSeconds(this.notBefore, this.expiration)

  // equality code
  def canEqual(other: Any): Boolean = other.isInstanceOf[JwtClaim]

  override def equals(other: Any): Boolean = other match {
    case that: JwtClaim =>
      (that.canEqual(this)) &&
      content == that.content &&
      issuer == that.issuer &&
      subject == that.subject &&
      audience == that.audience &&
      expiration == that.expiration &&
      notBefore == that.notBefore &&
      issuedAt == that.issuedAt &&
      jwtId == that.jwtId
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(content, issuer, subject, audience, expiration, notBefore, issuedAt, jwtId)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }

  override def toString: String =
    s"JwtClaim($content, $issuer, $subject, $audience, $expiration, $notBefore, $issuedAt, $jwtId)"
}


================================================
FILE: core/shared/src/main/scala/JwtCore.scala
================================================
package pdi.jwt

import java.security.{Key, PrivateKey, PublicKey}
import java.time.Clock
import javax.crypto.SecretKey
import scala.util.Try

import pdi.jwt.algorithms.*
import pdi.jwt.exceptions.*

/** Provide the main logic around Base64 encoding / decoding and signature using the correct
  * algorithm. '''H''' and '''C''' types are respesctively the header type and the claim type. For
  * the core project, they will be String but you are free to extend this trait using other types
  * like JsObject or anything else.
  *
  * Please, check implementations, like [[Jwt]], for code samples.
  *
  * @tparam H
  *   the type of the extracted header from a JSON Web Token
  * @tparam C
  *   the type of the extracted claim from a JSON Web Token
  *
  * @define token
  *   a JSON Web Token as a Base64 url-safe encoded String which can be used inside an HTTP header
  * @define headerString
  *   a valid stringified JSON representing the header of the token
  * @define claimString
  *   a valid stringified JSON representing the claim of the token
  * @define key
  *   the key that will be used to check the token signature
  * @define algo
  *   the algorithm to sign the token
  * @define algos
  *   a list of possible algorithms that the token can use. See
  *   [[https://jwt-scala.github.io/jwt-scala/#security-concerns Security concerns]] for more infos.
  */
trait JwtCore[H, C] {
  implicit private[jwt] def clock: Clock

  // Abstract methods
  protected def parseHeader(header: String): H
  protected def parseClaim(claim: String): C

  protected def extractAlgorithm(header: H): Option[JwtAlgorithm]
  protected def extractExpiration(claim: C): Option[Long]
  protected def extractNotBefore(claim: C): Option[Long]

  def encode(header: String, claim: String): String = {
    JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim) + "."
  }

  /** Encode a JSON Web Token from its different parts. Both the header and the claim will be
    * encoded to Base64 url-safe, then a signature will be eventually generated from it if you did
    * pass a key and an algorithm, and finally, those three parts will be merged as a single string,
    * using dots as separator.
    *
    * @return
    *   $token
    * @param header
    *   $headerString
    * @param claim
    *   $claimString
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(header: String, claim: String, key: String, algorithm: JwtAlgorithm): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  def encode(header: String, claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  def encode(
      header: String,
      claim: String,
      key: PrivateKey,
      algorithm: JwtAsymmetricAlgorithm
  ): String = {
    val data = JwtBase64.encodeString(header) + "." + JwtBase64.encodeString(claim)
    data + "." + JwtBase64.encodeString(JwtUtils.sign(data, key, algorithm))
  }

  /** An alias to `encode` which will provide an automatically generated header.
    *
    * @return
    *   $token
    * @param claim
    *   $claimString
    */
  def encode(claim: String): String = encode(JwtHeader().toJson, claim)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to
    * get rid of Option for the key and the algorithm.
    *
    * @return
    *   $token
    * @param claim
    *   $claimString
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: String, key: String, algorithm: JwtAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to
    * get rid of Option for the key and the algorithm.
    *
    * @return
    *   $token
    * @param claim
    *   $claimString
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: String, key: SecretKey, algorithm: JwtHmacAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and allowing you to
    * get rid of Option for the key and the algorithm.
    *
    * @return
    *   $token
    * @param claim
    *   $claimString
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =
    encode(JwtHeader(algorithm).toJson, claim, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and setting both key
    * and algorithm to None.
    *
    * @return
    *   $token
    * @param claim
    *   the claim of the JSON Web Token
    */
  def encode(claim: JwtClaim): String = encode(claim.toJson)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as
    * a case class.
    *
    * @return
    *   $token
    * @param claim
    *   the claim of the JSON Web Token
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: JwtClaim, key: String, algorithm: JwtAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as
    * a case class.
    *
    * @return
    *   $token
    * @param claim
    *   the claim of the JSON Web Token
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: JwtClaim, key: SecretKey, algorithm: JwtHmacAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` which will provide an automatically generated header and use the claim as
    * a case class.
    *
    * @return
    *   $token
    * @param claim
    *   the claim of the JSON Web Token
    * @param key
    *   $key
    * @param algorithm
    *   $algo
    */
  def encode(claim: JwtClaim, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): String =
    encode(claim.toJson, key, algorithm)

  /** An alias to `encode` if you want to use case classes for the header and the claim rather than
    * strings, they will just be stringified to JSON format.
    *
    * @return
    *   $token
    * @param header
    *   the header to stringify as a JSON before encoding the token
    * @param claim
    *   the claim to stringify as a JSON before encoding the token
    */
  def encode(header: JwtHeader, claim: JwtClaim): String = header.algorithm match {
    case None => encode(header.toJson, claim.toJson)
    case _    => throw new JwtNonEmptyAlgorithmException()
  }

  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be
    * deduced from the header.
    *
    * @return
    *   $token
    * @param header
    *   the header to stringify as a JSON before encoding the token
    * @param claim
    *   the claim to stringify as a JSON before encoding the token
    * @param key
    *   the secret key to use to sign the token (note that the algorithm will be deduced from the
    *   header)
    */
  def encode(header: JwtHeader, claim: JwtClaim, key: String): String = header.algorithm match {
    case Some(algo: JwtAlgorithm) => encode(header.toJson, claim.toJson, key, algo)
    case _                        => throw new JwtEmptyAlgorithmException()
  }

  /** An alias of `encode` if you only want to pass a string as the key, the algorithm will be
    * deduced from the header.
    *
    * @return
    *   $token
    * @param header
    *   the header to stringify as a JSON before encoding the token
    * @param claim
    *   the claim to stringify as a JSON before encoding the token
    * @param key
    *   the secret key to use to sign the token (note that the algorithm will be deduced from the
    *   header)
    */
  def encode(header: JwtHeader, claim: JwtClaim, key: Key): String = (header.algorithm, key) match {
    case (Some(algo: JwtHmacAlgorithm), k: SecretKey) =>
      encode(header.toJson, claim.toJson, k, algo)
    case (Some(algo: JwtAsymmetricAlgorithm), k: PrivateKey) =>
      encode(header.toJson, claim.toJson, k, algo)
    case _ =>
      throw new JwtValidationException(
        "The key type doesn't match the algorithm type. It's either a SecretKey and a HMAC algorithm or a PrivateKey and a RSA or ECDSA algorithm. And an algorithm is required of course."
      )
  }

  /** @return
    *   a tuple of (header64, header, claim64, claim, signature or empty string if none)
    * @throws JwtLengthException
    *   if there is not 2 or 3 parts in the token
    */
  private def splitToken(token: String): (String, String, String, String, String) = {
    val parts = JwtUtils.splitString(token, '.')

    val signature = parts.length match {
      case 2 => ""
      case 3 => parts(2)
      case _ =>
        throw new JwtLengthException(
          s"Expected token [$token] to be composed of 2 or 3 parts separated by dots."
        )
    }

    (
      parts(0),
      JwtBase64.decodeString(parts(0)),
      parts(1),
      JwtBase64.decodeString(parts(1)),
      signature
    )
  }

  /** Will try to decode a JSON Web Token to raw strings
    *
    * @return
    *   if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token
    *   $token
    */
  def decodeRawAll(token: String, options: JwtOptions): Try[(String, String, String)] = Try {
    val (_, header, _, claim, signature) = splitToken(token)
    validate(parseHeader(header), parseClaim(claim), signature, options)
    (header, claim, signature)
  }

  def decodeRawAll(token: String): Try[(String, String, String)] =
    decodeRawAll(token, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm
    *
    * @return
    *   if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRawAll(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
    (header, claim, signature)
  }

  def decodeRawAll(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm]
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm
    *
    * @return
    *   if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRawAll(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
    (header, claim, signature)
  }

  def decodeRawAll(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm]
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using a HMAC algorithm
    *
    * @return
    *   if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRawAll(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
    (header, claim, signature)
  }

  def decodeRawAll(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm]
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  def decodeRawAll(
      token: String,
      key: SecretKey,
      options: JwtOptions
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtAlgorithm.allHmac(), options)

  def decodeRawAll(token: String, key: SecretKey): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtOptions.DEFAULT)

  /** Will try to decode a JSON Web Token to raw strings using an asymmetric algorithm
    *
    * @return
    *   if successful, a tuple of 3 strings, the header, the claim and the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRawAll(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[(String, String, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
    (header, claim, signature)
  }

  def decodeRawAll(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm]
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, algorithms, JwtOptions.DEFAULT)

  def decodeRawAll(
      token: String,
      key: PublicKey,
      options: JwtOptions
  ): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtAlgorithm.allAsymmetric(), options)

  def decodeRawAll(token: String, key: PublicKey): Try[(String, String, String)] =
    decodeRawAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    */
  def decodeRaw(token: String, options: JwtOptions): Try[String] =
    decodeRawAll(token, options).map(_._2)

  def decodeRaw(token: String): Try[String] = decodeRaw(token, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRaw(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRaw(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm]
  ): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRaw(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decodeRaw(token: String, key: SecretKey, options: JwtOptions): Try[String] =
    decodeRaw(token, key, JwtAlgorithm.allHmac(), options)

  def decodeRaw(token: String, key: SecretKey): Try[String] =
    decodeRaw(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeRaw(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[String] =
    decodeRawAll(token, key, algorithms, options).map(_._2)

  def decodeRaw(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm]
  ): Try[String] =
    decodeRaw(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but only return the claim (you only care about the claim most of the
    * time)
    *
    * @return
    *   if successful, a string representing the JSON version of the claim
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decodeRaw(token: String, key: PublicKey, options: JwtOptions): Try[String] =
    decodeRaw(token, key, JwtAlgorithm.allAsymmetric(), options)

  def decodeRaw(token: String, key: PublicKey): Try[String] =
    decodeRaw(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    */
  def decodeAll(token: String, options: JwtOptions): Try[(H, C, String)] = Try {
    val (_, header, _, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(h, c, signature, options)
    (h, c, signature)
  }

  def decodeAll(token: String): Try[(H, C, String)] = decodeAll(token, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeAll(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm]
  ): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeAll(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm]
  ): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeAll(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm]
  ): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decodeAll(token: String, key: SecretKey, options: JwtOptions): Try[(H, C, String)] =
    decodeAll(token, key, JwtAlgorithm.allHmac(), options)

  def decodeAll(token: String, key: SecretKey): Try[(H, C, String)] =
    decodeAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decodeAll(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[(H, C, String)] = Try {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    val (h, c) = (parseHeader(header), parseClaim(claim))
    validate(header64, h, claim64, c, signature, key, algorithms, options)
    (h, c, signature)
  }

  def decodeAll(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm]
  ): Try[(H, C, String)] =
    decodeAll(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeRawAll` but return the real header and claim types
    *
    * @return
    *   if successful, a tuple representing the header, the claim and eventually the signature
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decodeAll(token: String, key: PublicKey, options: JwtOptions): Try[(H, C, String)] =
    decodeAll(token, key, JwtAlgorithm.allAsymmetric(), options)

  def decodeAll(token: String, key: PublicKey): Try[(H, C, String)] =
    decodeAll(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    */
  def decode(token: String, options: JwtOptions): Try[C] = decodeAll(token, options).map(_._2)

  def decode(token: String): Try[C] = decode(token, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decode(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decode(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decode(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decode(token: String, key: SecretKey, options: JwtOptions): Try[C] =
    decode(token, key, JwtAlgorithm.allHmac(), options)

  def decode(token: String, key: SecretKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def decode(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Try[C] =
    decodeAll(token, key, algorithms, options).map(_._2)

  def decode(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Try[C] =
    decode(token, key, algorithms, JwtOptions.DEFAULT)

  /** Same as `decodeAll` but only return the claim
    *
    * @return
    *   if successful, the claim of the token in its correct type
    * @param token
    *   $token
    * @param key
    *   $key
    */
  def decode(token: String, key: PublicKey, options: JwtOptions): Try[C] =
    decode(token, key, JwtAlgorithm.allAsymmetric(), options)

  def decode(token: String, key: PublicKey): Try[C] = decode(token, key, JwtOptions.DEFAULT)

  // Validate
  protected def validateTiming(claim: C, options: JwtOptions): Try[Unit] = {
    val maybeExpiration: Option[Long] =
      if (options.expiration) extractExpiration(claim) else None

    val maybeNotBefore: Option[Long] =
      if (options.notBefore) extractNotBefore(claim) else None

    JwtTime.validateNowIsBetweenSeconds(
      maybeNotBefore.map(_ - options.leeway),
      maybeExpiration.map(_ + options.leeway)
    )
  }

  // Validate if an algorithm is inside the authorized range
  protected def validateHmacAlgorithm(
      algorithm: JwtHmacAlgorithm,
      algorithms: Seq[JwtHmacAlgorithm]
  ): Boolean = algorithms.contains(algorithm)

  // Validate if an algorithm is inside the authorized range
  protected def validateAsymmetricAlgorithm(
      algorithm: JwtAsymmetricAlgorithm,
      algorithms: Seq[JwtAsymmetricAlgorithm]
  ): Boolean = algorithms.contains(algorithm)

  // Validation when no key and no algorithm (or unknown)
  protected def validate(header: H, claim: C, signature: String, options: JwtOptions) = {
    if (options.signature) {
      if (!signature.isEmpty) {
        throw new JwtNonEmptySignatureException()
      }

      extractAlgorithm(header).foreach {
        case JwtUnknownAlgorithm(name) => throw new JwtNonSupportedAlgorithm(name)
        case _                         => throw new JwtNonEmptyAlgorithmException()
      }
    }

    validateTiming(claim, options).get
  }

  // Validation when both key and algorithm
  protected def validate(
      header64: String,
      header: H,
      claim64: String,
      claim: C,
      signature: String,
      options: JwtOptions,
      verify: (Array[Byte], Array[Byte], JwtAlgorithm) => Boolean
  ): Unit = {
    if (options.signature) {
      val maybeAlgo = extractAlgorithm(header)

      if (options.signature && signature.isEmpty) {
        throw new JwtEmptySignatureException()
      } else if (maybeAlgo.isEmpty) {
        throw new JwtEmptyAlgorithmException()
      } else if (
        !verify(
          JwtUtils.bytify(header64 + "." + claim64),
          JwtBase64.decode(signature),
          maybeAlgo.get
        )
      ) {
        throw new JwtValidationException("Invalid signature for this token or wrong algorithm.")
      }
    }
    validateTiming(claim, options).get
  }

  // Generic validation on String Key for HMAC algorithms
  protected def validate(
      header64: String,
      header: H,
      claim64: String,
      claim: C,
      signature: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Unit = validate(
    header64,
    header,
    claim64,
    claim,
    signature,
    options,
    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>
      algorithm match {
        case algo: JwtHmacAlgorithm =>
          validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
        case _ => false
      }
  )

  // Generic validation on String Key for asymmetric algorithms
  protected def validate(
      header64: String,
      header: H,
      claim64: String,
      claim: C,
      signature: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Unit = validate(
    header64,
    header,
    claim64,
    claim,
    signature,
    options,
    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>
      algorithm match {
        case algo: JwtAsymmetricAlgorithm =>
          validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(
            data,
            signature,
            key,
            algo
          )
        case _ => false
      }
  )

  // Validation for HMAC algorithm using a SecretKey
  protected def validate(
      header64: String,
      header: H,
      claim64: String,
      claim: C,
      signature: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Unit = validate(
    header64,
    header,
    claim64,
    claim,
    signature,
    options,
    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>
      algorithm match {
        case algo: JwtHmacAlgorithm =>
          validateHmacAlgorithm(algo, algorithms) && JwtUtils.verify(data, signature, key, algo)
        case _ => false
      }
  )

  // Validation for RSA and ECDSA algorithms using PublicKey
  protected def validate(
      header64: String,
      header: H,
      claim64: String,
      claim: C,
      signature: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Unit = validate(
    header64,
    header,
    claim64,
    claim,
    signature,
    options,
    (data: Array[Byte], signature: Array[Byte], algorithm: JwtAlgorithm) =>
      algorithm match {
        case algo: JwtAsymmetricAlgorithm =>
          validateAsymmetricAlgorithm(algo, algorithms) && JwtUtils.verify(
            data,
            signature,
            key,
            algo
          )
        case _ => false
      }
  )

  /** Valid a token: doesn't return anything but will thrown exceptions if there are any errors.
    *
    * @param token
    *   $token
    * @throws JwtValidationException
    *   default validation exception
    * @throws JwtLengthException
    *   the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException
    *   the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException
    *   the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException
    *   couldn't decode the token since it's not a valid base64 string
    */
  def validate(token: String, options: JwtOptions): Unit = {
    val (_, header, _, claim, signature) = splitToken(token)
    validate(parseHeader(header), parseClaim(claim), signature, options)
  }

  def validate(token: String): Unit = validate(token, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key for HMAC algorithms.
    *
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    * @throws JwtValidationException
    *   default validation exception
    * @throws JwtLengthException
    *   the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException
    *   the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException
    *   the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException
    *   couldn't decode the token since it's not a valid base64 string
    */
  def validate(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
  }

  def validate(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Unit =
    validate(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key for asymmetric
    * algorithms.
    *
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    * @throws JwtValidationException
    *   default validation exception
    * @throws JwtLengthException
    *   the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException
    *   the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException
    *   the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException
    *   couldn't decode the token since it's not a valid base64 string
    */
  def validate(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
  }

  def validate(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Unit =
    validate(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key.
    *
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    * @throws JwtValidationException
    *   default validation exception
    * @throws JwtLengthException
    *   the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException
    *   the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException
    *   the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException
    *   couldn't decode the token since it's not a valid base64 string
    */
  def validate(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
  }

  def validate(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Unit =
    validate(token, key, algorithms, JwtOptions.DEFAULT)

  def validate(token: String, key: SecretKey, options: JwtOptions): Unit =
    validate(token, key, JwtAlgorithm.allHmac(), options)

  def validate(token: String, key: SecretKey): Unit = validate(token, key, JwtOptions.DEFAULT)

  /** An alias of `validate` in case you want to directly pass a string key.
    *
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    * @throws JwtValidationException
    *   default validation exception
    * @throws JwtLengthException
    *   the number of parts separated by dots is wrong
    * @throws JwtNotBeforeException
    *   the token isn't valid yet because its `notBefore` attribute is in the future
    * @throws JwtExpirationException
    *   the token isn't valid anymore because its `expiration` attribute is in the past
    * @throws IllegalArgumentException
    *   couldn't decode the token since it's not a valid base64 string
    */
  def validate(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Unit = {
    val (header64, header, claim64, claim, signature) = splitToken(token)
    validate(
      header64,
      parseHeader(header),
      claim64,
      parseClaim(claim),
      signature,
      key,
      algorithms,
      options
    )
  }

  def validate(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Unit =
    validate(token, key, algorithms, JwtOptions.DEFAULT)

  def validate(token: String, key: PublicKey, options: JwtOptions): Unit =
    validate(token, key, JwtAlgorithm.allAsymmetric(), options)

  def validate(token: String, key: PublicKey): Unit = validate(token, key, JwtOptions.DEFAULT)

  /** Test if a token is valid. Doesn't throw any exception.
    *
    * @return
    *   a boolean value indicating if the token is valid or not
    * @param token
    *   $token
    */
  def isValid(token: String, options: JwtOptions): Boolean = Try(validate(token, options)).isSuccess

  def isValid(token: String): Boolean = isValid(token, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key for HMAC algorithms
    *
    * @return
    *   a boolean value indicating if the token is valid or not
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def isValid(
      token: String,
      key: String,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess

  def isValid(token: String, key: String, algorithms: Seq[JwtHmacAlgorithm]): Boolean =
    isValid(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key for asymmetric
    * algorithms
    *
    * @return
    *   a boolean value indicating if the token is valid or not
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def isValid(
      token: String,
      key: String,
      algorithms: => Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess

  def isValid(token: String, key: String, algorithms: => Seq[JwtAsymmetricAlgorithm]): Boolean =
    isValid(token, key, algorithms, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key
    *
    * @return
    *   a boolean value indicating if the token is valid or not
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def isValid(
      token: String,
      key: SecretKey,
      algorithms: Seq[JwtHmacAlgorithm],
      options: JwtOptions
  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess

  def isValid(token: String, key: SecretKey, algorithms: Seq[JwtHmacAlgorithm]): Boolean =
    isValid(token, key, algorithms, JwtOptions.DEFAULT)

  def isValid(token: String, key: SecretKey, options: JwtOptions): Boolean =
    isValid(token, key, JwtAlgorithm.allHmac(), options)

  def isValid(token: String, key: SecretKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)

  /** An alias for `isValid` if you want to directly pass a string as the key
    *
    * @return
    *   a boolean value indicating if the token is valid or not
    * @param token
    *   $token
    * @param key
    *   $key
    * @param algorithms
    *   $algos
    */
  def isValid(
      token: String,
      key: PublicKey,
      algorithms: Seq[JwtAsymmetricAlgorithm],
      options: JwtOptions
  ): Boolean = Try(validate(token, key, algorithms, options)).isSuccess

  def isValid(token: String, key: PublicKey, algorithms: Seq[JwtAsymmetricAlgorithm]): Boolean =
    isValid(token, key, algorithms, JwtOptions.DEFAULT)

  def isValid(token: String, key: PublicKey, options: JwtOptions): Boolean =
    isValid(token, key, JwtAlgorithm.allAsymmetric(), options)

  def isValid(token: String, key: PublicKey): Boolean = isValid(token, key, JwtOptions.DEFAULT)
}


================================================
FILE: core/shared/src/main/scala/JwtException.scala
================================================
package pdi.jwt.exceptions

import pdi.jwt.JwtTime

sealed abstract class JwtException(message: String) extends RuntimeException(message)

class JwtLengthException(message: String) extends JwtException(message)

class JwtValidationException(message: String) extends JwtException(message)

class JwtSignatureFormatException(message: String) extends JwtException(message)

class JwtEmptySignatureException()
    extends JwtException(
      "No signature found inside the token while trying to verify it with a key."
    )

class JwtNonEmptySignatureException()
    extends JwtException(
      "Non-empty signature found inside the token while trying to verify without a key."
    )

class JwtEmptyAlgorithmException()
    extends JwtException(
      "No algorithm found inside the token header while having a key to sign or verify it."
    )

class JwtNonEmptyAlgorithmException()
    extends JwtException(
      "Algorithm found inside the token header while trying to sign or verify without a key."
    )

class JwtExpirationException(expiration: Long)
    extends JwtException("The token is expired since " + JwtTime.format(expiration))

class JwtNotBeforeException(notBefore: Long)
    extends JwtException("The token will only be valid after " + JwtTime.format(notBefore))

class JwtNonSupportedAlgorithm(algo: String)
    extends JwtException(s"The algorithm [$algo] is not currently supported.")

class JwtNonSupportedCurve(curve: String)
    extends JwtException(s"The curve [$curve] is not currently supported.")

class JwtNonStringException(val key: String)
    extends JwtException(s"During JSON parsing, expected a String for key [$key]") {
  @deprecated("Use key instead", since = "9.0.1")
  def getKey = key
}

object JwtNonStringException {
  def unapply(e: JwtNonStringException) = Some(e.key)
}

class JwtNonStringSetOrStringException(val key: String)
    extends JwtException(s"During JSON parsing, expected a Set[String] or String for key [$key]") {
  @deprecated("Use key instead", since = "9.0.1")
  def getKey = key
}

class JwtNonNumberException(val key: String)
    extends JwtException(s"During JSON parsing, expected a Number for key [$key]") {
  @deprecated("Use key instead", since = "9.0.1")
  def getKey = key
}

object JwtNonNumberException {
  def unapply(e: JwtNonNumberException) = Some(e.key)
}


================================================
FILE: core/shared/src/main/scala/JwtHeader.scala
================================================
package pdi.jwt

object JwtHeader {
  val DEFAULT_TYPE = "JWT"

  def apply(
      algorithm: Option[JwtAlgorithm] = None,
      typ: Option[String] = None,
      contentType: Option[String] = None,
      keyId: Option[String] = None
  ) = new JwtHeader(algorithm, typ, contentType, keyId)

  def apply(algorithm: Option[JwtAlgorithm]): JwtHeader = algorithm match {
    case Some(algo) => JwtHeader(algo)
    case _          => new JwtHeader(None, None, None, None)
  }

  def apply(algorithm: JwtAlgorithm): JwtHeader =
    new JwtHeader(Option(algorithm), Option(DEFAULT_TYPE), None, None)

  def apply(algorithm: JwtAlgorithm, typ: String): JwtHeader =
    new JwtHeader(Option(algorithm), Option(typ), None, None)

  def apply(algorithm: JwtAlgorithm, typ: String, contentType: String): JwtHeader =
    new JwtHeader(Option(algorithm), Option(typ), Option(contentType), None)

  def apply(algorithm: JwtAlgorithm, typ: String, contentType: String, keyId: String): JwtHeader =
    new JwtHeader(Option(algorithm), Option(typ), Option(contentType), Option(keyId))
}

class JwtHeader(
    val algorithm: Option[JwtAlgorithm],
    val typ: Option[String],
    val contentType: Option[String],
    val keyId: Option[String]
) {
  def toJson: String = JwtUtils.hashToJson(
    Seq(
      "typ" -> typ,
      "alg" -> algorithm.map(_.name).orElse(Option("none")),
      "cty" -> contentType,
      "kid" -> keyId
    ).collect { case (key, Some(value)) =>
      (key -> value)
    }
  )

  /** Assign the type to the header */
  def withType(typ: String): JwtHeader = {
    JwtHeader(algorithm, Option(typ), contentType, keyId)
  }

  /** Assign the default type `JWT` to the header */
  def withType: JwtHeader = this.withType(JwtHeader.DEFAULT_TYPE)

  /** Assign a key id to the header */
  def withKeyId(keyId: String): JwtHeader = {
    JwtHeader(algorithm, typ, contentType, Option(keyId))
  }

  // equality code
  def canEqual(other: Any): Boolean = other.isInstanceOf[JwtHeader]

  override def equals(other: Any): Boolean = other match {
    case that: JwtHeader =>
      (that.canEqual(this)) &&
      algorithm == that.algorithm &&
      typ == that.typ &&
      contentType == that.contentType &&
      keyId == that.keyId
    case _ => false
  }

  override def hashCode(): Int = {
    val state = Seq(algorithm, typ, contentType, keyId)
    state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
  }

  override def toString: String = s"JwtHeader($algorithm, $typ, $contentType, $keyId)"
}


================================================
FILE: core/shared/src/main/scala/JwtOptions.scala
================================================
package pdi.jwt

case class JwtOptions(
    signature: Boolean = true,
    expiration: Boolean = true,
    notBefore: Boolean = true,
    leeway: Long = 0 // in seconds
)

object JwtOptions {
  val DEFAULT = new JwtOptions()
}


================================================
FILE: core/shared/src/main/scala/JwtTime.scala
================================================
package pdi.jwt

import java.time.{Clock, Instant}
import scala.util.{Failure, Success, Try}

import pdi.jwt.exceptions.{JwtExpirationException, JwtNotBeforeException}

/** Util object to handle time operations */
object JwtTime {

  /** Returns the number of millis since the 01.01.1970
    *
    * @return
    *   the number of millis since the 01.01.1970
    */
  def now(implicit clock: Clock): Long = clock.instant().toEpochMilli

  /** Returns the number of seconds since the 01.01.1970
    *
    * @return
    *   the number of seconds since the 01.01.1970
    */
  def nowSeconds(implicit clock: Clock): Long = this.now / 1000

  def format(time: Long): String = Instant.ofEpochMilli(time).toString

  /** Test if the current time is between the two prams
    *
    * @return
    *   the result of the test
    * @param start
    *   if set, the instant that must be before now (in millis)
    * @param end
    *   if set, the instant that must be after now (in millis)
    */
  def nowIsBetween(start: Option[Long], end: Option[Long])(implicit clock: Clock): Boolean =
    validateNowIsBetween(start, end).isSuccess

  /** Same as `nowIsBetween` but using seconds rather than millis.
    *
    * @param start
    *   if set, the instant that must be before now (in seconds)
    * @param end
    *   if set, the instant that must be after now (in seconds)
    */
  def nowIsBetweenSeconds(start: Option[Long], end: Option[Long])(implicit clock: Clock): Boolean =
    nowIsBetween(start.map(_ * 1000), end.map(_ * 1000))

  /** Test if the current time is between the two params and throw an exception if we don't have
    * `start` <= now < `end`
    *
    * @param start
    *   if set, the instant that must be before now (in millis)
    * @param end
    *   if set, the instant that must be after now (in millis)
    * @return
    *   Failure(JwtNotBeforeException) if `start` > now, Failure(JwtExpirationException) if now >=
    *   `end`
    */
  def validateNowIsBetween(start: Option[Long], end: Option[Long])(implicit
      clock: Clock
  ): Try[Unit] = {
    val timeNow = now

    (start, end) match {
      case (Some(s), _) if s > timeNow  => Failure(new JwtNotBeforeException(s))
      case (_, Some(e)) if e <= timeNow => Failure(new JwtExpirationException(e))
      case _                            => Success(())
    }
  }

  /** Same as `validateNowIsBetween` but using seconds rather than millis.
    *
    * @param start
    *   if set, the instant that must be before now (in seconds)
    * @param end
    *   if set, the instant that must be after now (in seconds)
    * @return
    *   Failure(JwtNotBeforeException) if `start` > now, Failure(JwtExpirationException) if now >
    *   `end`
    */
  def validateNowIsBetweenSeconds(start: Option[Long], end: Option[Long])(implicit
      clock: Clock
  ): Try[Unit] =
    validateNowIsBetween(start.map(_ * 1000), end.map(_ * 1000))
}


================================================
FILE: core/shared/src/main/scala/JwtUtils.scala
================================================
package pdi.jwt

import java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec}
import java.security.{KeyFactory, PrivateKey, PublicKey, Signature}
import javax.crypto.spec.SecretKeySpec
import javax.crypto.{Mac, SecretKey}

import pdi.jwt.JwtAlgorithm.{ES256, ES384, ES512}
import pdi.jwt.algorithms.*
import pdi.jwt.exceptions.{JwtNonSupportedAlgorithm, JwtSignatureFormatException}

object JwtUtils {
  val ENCODING = "UTF-8"
  val RSA = "RSA"
  val ECDSA = "EC"
  val EdDSA = "EdDSA"

  /** Convert an array of bytes to its corresponding string using the default encoding.
    *
    * @return
    *   the final string
    * @param arr
    *   the array of bytes to transform
    */
  def stringify(arr: Array[Byte]): String = new String(arr, ENCODING)

  /** Convert a string to its corresponding array of bytes using the default encoding.
    *
    * @return
    *   the final array of bytes
    * @param str
    *   the string to convert
    */
  def bytify(str: String): Array[Byte] = str.getBytes(ENCODING)

  private def escape(value: String): String = value.replaceAll("\"", "\\\\\"")

  /** Convert a sequence to a JSON array
    */
  def seqToJson(seq: Seq[Any]): String = seq
    .map {
      case value: String        => "\"" + escape(value) + "\""
      case value: Boolean       => if (value) "true" else "false"
      case value: Double        => value.toString
      case value: Short         => value.toString
      case value: Float         => value.toString
      case value: Long          => value.toString
      case value: Int           => value.toString
      case value: BigDecimal    => value.toString
      case value: BigInt        => value.toString
      case (key: String, value) => hashToJson(Seq(key -> value))
      case value: Any           => "\"" + escape(value.toString) + "\""
    }
    .mkString("[", ",", "]")

  /** Convert a sequence of tuples to a JSON object
    */
  def hashToJson(hash: Seq[(String, Any)]): String = hash
    .map {
      case (key, value: String)     => "\"" + escape(key) + "\":\"" + escape(value) + "\""
      case (key, value: Boolean)    => "\"" + escape(key) + "\":" + (if (value) "true" else "false")
      case (key, value: Double)     => "\"" + escape(key) + "\":" + value.toString
      case (key, value: Short)      => "\"" + escape(key) + "\":" + value.toString
      case (key, value: Float)      => "\"" + escape(key) + "\":" + value.toString
      case (key, value: Long)       => "\"" + escape(key) + "\":" + value.toString
      case (key, value: Int)        => "\"" + escape(key) + "\":" + value.toString
      case (key, value: BigDecimal) => "\"" + escape(key) + "\":" + value.toString
      case (key, value: BigInt)     => "\"" + escape(key) + "\":" + value.toString
      case (key, (vKey: String, vValue)) =>
        "\"" + escape(key) + "\":" + hashToJson(Seq(vKey -> vValue))
      case (key, value: Seq[Any]) => "\"" + escape(key) + "\":" + seqToJson(value)
      case (key, value: Set[_])   => "\"" + escape(key) + "\":" + seqToJson(value.toSeq)
      case (key, value: Any)      => "\"" + escape(key) + "\":\"" + escape(value.toString) + "\""
    }
    .mkString("{", ",", "}")

  /** Merge multiple JSON strings to a unique one
    */
  def mergeJson(json: String, jsonSeq: String*): String = {
    val initJson = json.trim match {
      case ""    => ""
      case value => value.drop(1).dropRight(1)
    }

    "{" + jsonSeq.map(_.trim).fold(initJson) {
      case (j1, result) if j1.length < 5 => result.drop(1).dropRight(1)
      case (result, j2) if j2.length < 7 => result
      case (j1, j2)                      => j1 + "," + j2.drop(1).dropRight(1)
    } + "}"
  }

  private def parseKey(key: String): Array[Byte] = JwtBase64.decodeNonSafe(
    key.replaceAll("-----BEGIN ([^-]*)-----|-----END ([^-]*)-----|\\s*", "")
  )

  private def parsePrivateKey(key: String, keyAlgo: String) = {
    val spec = new PKCS8EncodedKeySpec(parseKey(key))
    KeyFactory.getInstance(keyAlgo).generatePrivate(spec)
  }

  private def parsePublicKey(key: String, keyAlgo: String): PublicKey = {
    val spec = new X509EncodedKeySpec(parseKey(key))
    KeyFactory.getInstance(keyAlgo).generatePublic(spec)
  }

  /** Generate the signature for a given data using the key and HMAC algorithm provided.
    */
  def sign(data: Array[Byte], key: SecretKey, algorithm: JwtHmacAlgorithm): Array[Byte] = {
    val mac = Mac.getInstance(algorithm.fullName)
    mac.init(key)
    mac.doFinal(data)
  }

  def sign(data: String, key: SecretKey, algorithm: JwtHmacAlgorithm): Array[Byte] =
    sign(bytify(data), key, algorithm)

  /** Generate the signature for a given data using the key and RSA or ECDSA algorithm provided.
    */
  def sign(data: Array[Byte], key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): Array[Byte] = {
    val signer = Signature.getInstance(algorithm.fullName)
    signer.initSign(key)
    signer.update(data)
    algorithm match {
      case _: JwtRSAAlgorithm           => signer.sign
      case algorithm: JwtECDSAAlgorithm =>
        transcodeSignatureToConcat(signer.sign, getSignatureByteArrayLength(algorithm))
      case _: JwtEdDSAAlgorithm => signer.sign
    }
  }

  def sign(data: String, key: PrivateKey, algorithm: JwtAsymmetricAlgorithm): Array[Byte] =
    sign(bytify(data), key, algorithm)

  /** Will try to sign some given data by parsing the provided key, if parsing fail, please consider
    * retrieving the SecretKey or the PrivateKey on your side and then use another "sign" method.
    */
  def sign(data: Array[Byte], key: String, algorithm: JwtAlgorithm): Array[Byte] =
    algorithm match {
      case algo: JwtHmacAlgorithm => sign(data, new SecretKeySpec(bytify(key), algo.fullName), algo)
      case algo: JwtRSAAlgorithm  => sign(data, parsePrivateKey(key, RSA), algo)
      case algo: JwtECDSAAlgorithm   => sign(data, parsePrivateKey(key, ECDSA), algo)
      case algo: JwtEdDSAAlgorithm   => sign(data, parsePrivateKey(key, EdDSA), algo)
      case algo: JwtUnknownAlgorithm => throw new JwtNonSupportedAlgorithm(algo.fullName)
    }

  /** Alias to `sign` using a String data which will be converted to an array of bytes.
    */
  def sign(data: String, key: String, algorithm: JwtAlgorithm): Array[Byte] =
    sign(bytify(data), key, algorithm)

  /** Check if a signature is valid for a given data using the key and the HMAC algorithm provided.
    */
  def verify(
      data: Array[Byte],
      signature: Array[Byte],
      key: SecretKey,
      algorithm: JwtHmacAlgorithm
  ): Boolean = {
    JwtArrayUtils.constantTimeAreEqual(sign(data, key, algorithm), signature)
  }

  /** Check if a signature is valid for a given data using the key and the RSA or ECDSA algorithm
    * provided.
    */
  def verify(
      data: Array[Byte],
      signature: Array[Byte],
      key: PublicKey,
      algorithm: JwtAsymmetricAlgorithm
  ): Boolean = {
    val signer = Signature.getInstance(algorithm.fullName)
    signer.initVerify(key)
    signer.update(data)
    algorithm match {
      case _: JwtRSAAlgorithm   => signer.verify(signature)
      case _: JwtECDSAAlgorithm => signer.verify(transcodeSignatureToDER(signature))
      case _: JwtEdDSAAlgorithm => signer.verify(signature)
    }
  }

  /** Will try to check if a signature is valid for a given data by parsing the provided key, if
    * parsing fail, please consider retrieving the SecretKey or the PublicKey on your side and then
    * use another "verify" method.
    */
  def verify(
      data: Array[Byte],
      signature: Array[Byte],
      key: String,
      algorithm: JwtAlgorithm
  ): Boolean = algorithm match {
    case algo: JwtHmacAlgorithm =>
      verify(data, signature, new SecretKeySpec(bytify(key), algo.fullName), algo)
    case algo: JwtRSAAlgorithm     => verify(data, signature, parsePublicKey(key, RSA), algo)
    case algo: JwtECDSAAlgorithm   => verify(data, signature, parsePublicKey(key, ECDSA), algo)
    case algo: JwtEdDSAAlgorithm   => verify(data, signature, parsePublicKey(key, EdDSA), algo)
    case algo: JwtUnknownAlgorithm => throw new JwtNonSupportedAlgorithm(algo.fullName)
  }

  /** Alias for `verify`
    */
  def verify(data: String, signature: String, key: String, algorithm: JwtAlgorithm): Boolean =
    verify(bytify(data), bytify(signature), key, algorithm)

  /** Returns the expected signature byte array length (R + S parts) for the specified ECDSA
    * algorithm.
    *
    * @param algorithm
    *   The ECDSA algorithm. Must be supported and not {@code null} .
    * @return
    *   The expected byte array length for the signature.
    */
  def getSignatureByteArrayLength(algorithm: JwtECDSAAlgorithm): Int = algorithm match {
    case ES256 => 64
    case ES384 => 96
    case ES512 => 132
  }

  /** Transcodes the JCA ASN.1/DER-encoded signature into the concatenated R + S format expected by
    * ECDSA JWS.
    *
    * @param derSignature
    *   The ASN1./DER-encoded. Must not be {@code null} .
    * @param outputLength
    *   The expected length of the ECDSA JWS signature.
    * @return
    *   The ECDSA JWS encoded signature.
    * @throws JwtSignatureFormatException
    *   If the ASN.1/DER signature format is invalid.
    */
  @throws[JwtSignatureFormatException]
  def transcodeSignatureToConcat(derSignature: Array[Byte], outputLength: Int): Array[Byte] = {
    if (derSignature.length < 8 || derSignature(0) != 48)
      throw new JwtSignatureFormatException("Invalid ECDSA signature format")

    val offset: Int = derSignature(1) match {
      case s if s > 0            => 2
      case s if s == 0x81.toByte => 3
      case _ => throw new JwtSignatureFormatException("Invalid ECDSA signature format")
    }

    val rLength: Byte = derSignature(offset + 1)
    var i = rLength.toInt
    while ((i > 0) && (derSignature((offset + 2 + rLength) - i) == 0)) {
      i -= 1
    }

    val sLength: Byte = derSignature(offset + 2 + rLength + 1)
    var j = sLength.toInt
    while ((j > 0) && (derSignature((offset + 2 + rLength + 2 + sLength) - j) == 0)) {
      j -= 1
    }

    val rawLen: Int = Math.max(Math.max(i, j), outputLength / 2)

    if (
      (derSignature(offset - 1) & 0xff) != derSignature.length - offset
      || (derSignature(offset - 1) & 0xff) != 2 + rLength + 2 + sLength
      || derSignature(offset) != 2 || derSignature(offset + 2 + rLength) != 2
    )
      throw new JwtSignatureFormatException("Invalid ECDSA signature format")

    val concatSignature: Array[Byte] = new Array[Byte](2 * rawLen)
    System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i)
    System.arraycopy(
      derSignature,
      (offset + 2 + rLength + 2 + sLength) - j,
      concatSignature,
      2 * rawLen - j,
      j
    )
    concatSignature
  }

  /** Transcodes the ECDSA JWS signature into ASN.1/DER format for use by the JCA verifier.
    *
    * @param signature
    *   The JWS signature, consisting of the concatenated R and S values. Must not be {@code null} .
    * @return
    *   The ASN.1/DER encoded signature.
    * @throws JwtSignatureFormatException
    *   If the ECDSA JWS signature format is invalid.
    */
  @throws[JwtSignatureFormatException]
  def transcodeSignatureToDER(signature: Array[Byte]): Array[Byte] = {
    var (r, s) = signature.splitAt(signature.length / 2)
    r = r.dropWhile(_ == 0)
    if (r.length > 0 && r(0) < 0)
      r +:= 0.toByte

    s = s.dropWhile(_ == 0)
    if (s.length > 0 && s(0) < 0)
      s +:= 0.toByte

    val signatureLength = 2 + r.length + 2 + s.length

    if (signatureLength > 255)
      throw new JwtSignatureFormatException("Invalid ECDSA signature format")

    val signatureDER = scala.collection.mutable.ListBuffer.empty[Byte]
    signatureDER += 48
    if (signatureLength >= 128)
      signatureDER += 0x81.toByte

    signatureDER += signatureLength.toByte
    signatureDER ++= (Seq(2.toByte, r.length.toByte) ++ r)
    signatureDER ++= (Seq(2.toByte, s.length.toByte) ++ s)

    signatureDER.toArray
  }

  def splitString(input: String, separator: Char): Array[String] = {
    val builder = scala.collection.mutable.ArrayBuffer.empty[String]
    var lastIndex = 0
    var index = input.indexOf(separator.toInt, lastIndex)
    while (index != -1) {
      builder += input.substring(lastIndex, index)
      lastIndex = index + 1
      index = input.indexOf(separator.toInt, lastIndex)
    }
    // Add the remainder
    if (lastIndex < input.length) {
      builder += input.substring(lastIndex, input.length)
    }
    builder.toArray
  }

}


================================================
FILE: core/shared/src/test/scala/Fixture.scala
================================================
package pdi.jwt

import java.security.spec.*
import java.security.{KeyFactory, KeyPairGenerator, SecureRandom, Security}
import java.time.{Clock, Instant, ZoneOffset}
import javax.crypto.spec.SecretKeySpec

import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import pdi.jwt.algorithms.{JwtECDSAAlgorithm, JwtHmacAlgorithm, JwtRSAAlgorithm}

trait DataEntryBase {
  def algo: JwtAlgorithm
  def header: String
  def headerClass: JwtHeader
  def header64: String
  def signature: String
  def token: String
  def tokenUnsigned: String
  def tokenEmpty: String
}

case class DataEntry[A <: JwtAlgorithm](
    algo: A,
    header: String,
    headerClass: JwtHeader,
    header64: String,
    signature: String,
    token: String = "",
    tokenUnsigned: String = "",
    tokenEmpty: String = ""
) extends DataEntryBase

trait ClockFixture {
  val expiration: Long = 1300819380
  val expirationMillis: Long = expiration * 1000
  val beforeExpirationMillis: Long = expirationMillis - 1
  val afterExpirationMillis: Long = expirationMillis + 1

  def fixedUTC(millis: Long): Clock = Clock.fixed(Instant.ofEpochMilli(millis), ZoneOffset.UTC)

  val afterExpirationClock: Clock = fixedUTC(afterExpirationMillis)

  val notBefore: Long = 1300819320
  val notBeforeMillis: Long = notBefore * 1000
  val beforeNotBeforeMillis: Long = notBeforeMillis - 1
  val afterNotBeforeMillis: Long = notBeforeMillis + 1

  val beforeNotBeforeClock: Clock = fixedUTC(beforeNotBeforeMillis)
  val afterNotBeforeClock: Clock = fixedUTC(afterNotBeforeMillis)

  val validTime: Long = (expiration + notBefore) / 2
  val validTimeMillis: Long = validTime * 1000
  val validTimeClock: Clock = fixedUTC(validTimeMillis)

  val ecCurveName = "secp521r1"
}

trait Fixture extends ClockFixture {

  // Bouncycastle is not included by default. Add it for each test.
  if (Security.getProvider("BC") == null) {
    Security.addProvider(new BouncyCastleProvider())
    ()
  }

  val Ed25519 = "Ed25519"

  val secretKey =
    "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
  val secretKeyBytes = JwtUtils.bytify(secretKey)
  def secretKeyOf(algo: JwtAlgorithm) = new SecretKeySpec(secretKeyBytes, algo.fullName)

  val claim = s"""{"iss":"joe","exp":$expiration,"http://example.com/is_root":true}"""
  val claimClass = JwtClaim(
    """{"http://example.com/is_root":true}""",
    issuer = Option("joe"),
    expiration = Option(expiration)
  )
  val claim64 =
    "eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ"

  val headerEmpty = """{"alg":"none"}"""
  val headerClassEmpty = JwtHeader()
  val header64Empty = "eyJhbGciOiJub25lIn0"

  val tokenEmpty = header64Empty + "." + claim64 + "."

  val headerWithSpaces = """{"alg"  :   "none"}"""
  val claimWithSpaces = """{"nbf"  :0  , "foo"  : "bar"  , "exp":    32086368000}"""
  val tokenWithSpaces =
    JwtBase64.encodeString(headerWithSpaces) + "." + JwtBase64.encodeString(claimWithSpaces) + "."

  val publicKeyRSA = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvzoCEC2rpSpJQaWZbUml
sDNwp83Jr4fi6KmBWIwnj1MZ6CUQ7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHu
vyrVfwY0dINk+nkqB74QcT2oCCH9XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/
W7DZCKXkC8fFPFj6QF5ZzApDw2Qsu3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN
40vXv9c4xiSafVvnx9BwYL7H1Q8NiK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQ
Cwyzee7bOJqXUDAuLcFARzPw1EsZAyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpp
tQIDAQAB
-----END PUBLIC KEY-----"""

  val privateKeyRSA = """-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAvzoCEC2rpSpJQaWZbUmlsDNwp83Jr4fi6KmBWIwnj1MZ6CUQ
7rBasuLI8AcfX5/10scSfQNCsTLV2tMKQaHuvyrVfwY0dINk+nkqB74QcT2oCCH9
XduJjDuwWA4xLqAKuF96FsIes52opEM50W7/W7DZCKXkC8fFPFj6QF5ZzApDw2Qs
u3yMRmr7/W9uWeaTwfPx24YdY7Ah+fdLy3KN40vXv9c4xiSafVvnx9BwYL7H1Q8N
iK9LGEN6+JSWfgckQCs6UUBOXSZdreNN9zbQCwyzee7bOJqXUDAuLcFARzPw1EsZ
AyjVtGCKIQ0/btqK+jFunT2NBC8RItanDZpptQIDAQABAoIBAQCsssO4Pra8hFMC
gX7tr0x+tAYy1ewmpW8stiDFilYT33YPLKJ9HjHbSms0MwqHftwwTm8JDc/GXmW6
qUui+I64gQOtIzpuW1fvyUtHEMSisI83QRMkF6fCSQm6jJ6oQAtOdZO6R/gYOPNb
3gayeS8PbMilQcSRSwp6tNTVGyC33p43uUUKAKHnpvAwUSc61aVOtw2wkD062XzM
hJjYpHm65i4V31AzXo8HF42NrAtZ8K/AuQZne5F/6F4QFVlMKzUoHkSUnTp60XZx
X77GuyDeDmCgSc2J7xvR5o6VpjsHMo3ek0gJk5ZBnTgkHvnpbULCRxTmDfjeVPue
v3NN2TBFAoGBAPxbqNEsXPOckGTvG3tUOAAkrK1hfW3TwvrW/7YXg1/6aNV4sklc
vqn/40kCK0v9xJIv9FM/l0Nq+CMWcrb4sjLeGwHAa8ASfk6hKHbeiTFamA6FBkvQ
//7GP5khD+y62RlWi9PmwJY21lEkn2mP99THxqvZjQiAVNiqlYdwiIc7AoGBAMH8
f2Ay7Egc2KYRYU2qwa5E/Cljn/9sdvUnWM+gOzUXpc5sBi+/SUUQT8y/rY4AUVW6
YaK7chG9YokZQq7ZwTCsYxTfxHK2pnG/tXjOxLFQKBwppQfJcFSRLbw0lMbQoZBk
S+zb0ufZzxc2fJfXE+XeJxmKs0TS9ltQuJiSqCPPAoGBALEc84K7DBG+FGmCl1sb
ZKJVGwwknA90zCeYtadrIT0/VkxchWSPvxE5Ep+u8gxHcqrXFTdILjWW4chefOyF
5ytkTrgQAI+xawxsdyXWUZtd5dJq8lxLtx9srD4gwjh3et8ZqtFx5kCHBCu29Fr2
PA4OmBUMfrs0tlfKgV+pT2j5AoGBAKnA0Z5XMZlxVM0OTH3wvYhI6fk2Kx8TxY2G
nxsh9m3hgcD/mvJRjEaZnZto6PFoqcRBU4taSNnpRr7+kfH8sCht0k7D+l8AIutL
ffx3xHv9zvvGHZqQ1nHKkaEuyjqo+5kli6N8QjWNzsFbdvBQ0CLJoqGhVHsXuWnz
W3Z4cBbVAoGAEtnwY1OJM7+R2u1CW0tTjqDlYU2hUNa9t1AbhyGdI2arYp+p+umA
b5VoYLNsdvZhqjVFTrYNEuhTJFYCF7jAiZLYvYm0C99BqcJnJPl7JjWynoNHNKw3
9f6PIOE1rAmPE8Cfz/GFF5115ZKVlq+2BY8EKNxbCIy2d/vMEvisnXI=
-----END RSA PRIVATE KEY-----"""

  val generatorRSA = KeyPairGenerator.getInstance(JwtUtils.RSA)
  generatorRSA.initialize(1024)
  val randomRSAKey = generatorRSA.generateKeyPair()

  val ecGenSpec = new ECGenParameterSpec(ecCurveName)
  val generatorEC = KeyPairGenerator.getInstance(JwtUtils.ECDSA)
  generatorEC.initialize(ecGenSpec, new SecureRandom())

  val randomECKey = generatorEC.generateKeyPair()

  val S = BigInt(
    "1ed498eedf499e5dd12b1ab94ee03d1a722eaca3ed890630c8b25f1015dd4ec5630a02ddb603f3248a3b87c88637e147ecc7a6e2a1c2f9ff1103be74e5d42def37d",
    16
  )
  val X = BigInt(
    "16528ac15dc4c8e0559fad628ac3ffbf5c7cfefe12d50a97c7d088cc10b408d4ab03ac0d543bde862699a74925c1f2fe7c247c00fddc1442099dfa0671fc032e10a",
    16
  )
  val Y = BigInt(
    "b7f22b3c1322beef766cadd1a5f0363840195b7be10d9a518802d8d528e03bc164c9588c5e63f1473d05195510676008b6808508539367d2893e1aa4b7cb9f9dab",
    16
  )

  val curveParams = ECNamedCurveTable.getParameterSpec(ecCurveName)
  val curveSpec: ECParameterSpec = new ECNamedCurveSpec(
    ecCurveName,
    curveParams.getCurve(),
    curveParams.getG(),
    curveParams.getN(),
    curveParams.getH()
  )

  val privateSpec = new ECPrivateKeySpec(S.underlying(), curveSpec)
  val publicSpec = new ECPublicKeySpec(new ECPoint(X.underlying(), Y.underlying()), curveSpec)

  val privateKeyEC = KeyFactory.getInstance(JwtUtils.ECDSA).generatePrivate(privateSpec)
  val publicKeyEC = KeyFactory.getInstance(JwtUtils.ECDSA).generatePublic(publicSpec)

  def setToken[A <: JwtAlgorithm](entry: DataEntry[A]): DataEntry[A] = {
    entry.copy(
      token = Seq(entry.header64, claim64, entry.signature).mkString("."),
      tokenUnsigned = Seq(entry.header64, claim64, "").mkString("."),
      tokenEmpty = Seq(header64Empty, claim64, "").mkString(".")
    )
  }

  val privateKeyEd25519 = "MC4CAQAwBQYDK2VwBCIEIHf3EQMqRKbBYOEjmrRm6Zu5hIYombr3DoWaRjZqK7uv"
  val publicKeyEd25519 = "MCowBQYDK2VwAyEAMGx9f797iAEdcI/QULMQFxgnt3ANZAqlTHavvAf3nD4="

  val generatorEd25519 = KeyPairGenerator.getInstance(Ed25519)
  val randomEd25519Key = generatorEd25519.generateKeyPair()

  val data = Seq(
    DataEntry[JwtHmacAlgorithm](
      JwtAlgorithm.HMD5,
      """{"typ":"JWT","alg":"HMD5"}""",
      JwtHeader(JwtAlgorithm.HMD5, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJITUQ1In0",
      "BVRxj65Lk3DXIug2IosRvw"
    ),
    DataEntry[JwtHmacAlgorithm](
      JwtAlgorithm.HS256,
      """{"typ":"JWT","alg":"HS256"}""",
      JwtHeader(JwtAlgorithm.HS256, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
      "IPSERPZc5wyxrZ4Yiq7l31wFk_qaDY5YrnfLjIC0Lmc"
    ),
    DataEntry[JwtHmacAlgorithm](
      JwtAlgorithm.HS384,
      """{"typ":"JWT","alg":"HS384"}""",
      JwtHeader(JwtAlgorithm.HS384, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9",
      "tCjCk4PefnNV6E_PByT5xumMVm6KAt_asxP8DXwcDnwsldVJi_Y7SfTVJzvyuGBY"
    ),
    DataEntry[JwtHmacAlgorithm](
      JwtAlgorithm.HS512,
      """{"typ":"JWT","alg":"HS512"}""",
      JwtHeader(JwtAlgorithm.HS512, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9",
      "ngZsdQj8p2wvUAo8xCbJPwganGPnG5UnLkg7VrE6NgmQdV16UITjlBajZxcai_U5PjQdeN-yJtyA5kxf8O5BOQ"
    )
  ).map(setToken)

  val dataRSA = Seq(
    DataEntry[JwtRSAAlgorithm](
      JwtAlgorithm.RS256,
      """{"typ":"JWT","alg":"RS256"}""",
      JwtHeader(JwtAlgorithm.RS256, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9",
      "h943pccw-3rOGJW_RlURooRk7SawDZcQiyP9iziq_LUKHtZME_UMGATeVpoc1aoGK0SlWPlVgV1HaB9fNEyziRYPi7i2K_l9XysRHhdo-luCL_D2rNK4Kv_034knQdC_pZPQ4vMviLDqHVL7w0edG-5-96fzFiP3jwV7FIz7r86fvtNgmKw8cH-cSZfEbj_vgWXT_bE_MHcCE0g4UBiXvTUbd9FpkiTugM6Lr9SXLiFKUtAraOxaKKeZ0VSLMTATK8M2PqLq4I0NnJMaZpcIp1pP9DFz07GomTpMP49Ag4CGzutFIUXz-J277OYDrLjfIT7jDnQIYuzrwE3vatwp2g"
    ),
    DataEntry[JwtRSAAlgorithm](
      JwtAlgorithm.RS384,
      """{"typ":"JWT","alg":"RS384"}""",
      JwtHeader(JwtAlgorithm.RS384, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9",
      "jpusk3t1NPdT7VaZLB6mO3_L4R59gSbgRM866HVZzN6qkH3vYy9y91eMs6YQZLXgg1nBi1ZY8pb4R9G_on4Xsenh-K7odRCHX-XzVbzAtnljMMChdqKp7zTAlAWF03ZrFyv91kxAQeyQSkwxDP4vP70SCLtt3_kevAzon5fE1L1DD1TNySe52TDCofd2RUPFhWzsfdAPvo_Qj1s_zG-DThHSMXXMY9GOtugyJjbDCDrl8uGeF_0XQm-wBuYQ_EGw0S9TsoI_8dggmeEyv8XwT2XKB20fKOc298GNWJ6q6E01hI0EjmWKXEtTyLG0edAF-QrNkXtkz-yX9WJmjmyVfA"
    ),
    DataEntry[JwtRSAAlgorithm](
      JwtAlgorithm.RS512,
      """{"typ":"JWT","alg":"RS512"}""",
      JwtHeader(JwtAlgorithm.RS512, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9",
      "UJLq3LjgGgxQHpIHYMc48mCW1GrqwNT4sVF7IpyT6Vtbk0b_TcZ-MoWzkdYnP4-V0D8fl7kxJlLooXDWVso25UQMC66t35pAjFsQHvz7WGn1MQf5F2IOVeS2T_Qg0ckfhykw-jqXgOCrtgI-8lq_A0W8lATLoWjaQSosZxH7oYk6XJY3v5gi3reurAsrbqRCi6Gc87MdB_Yl29acAMr2_G3hun6h_VJckemOsBudLf8kGj_3lCSCY8TLncJYTLB9ZAtWhS92LpKRwPGS2CED2sQcHbq4BK10yJh-YrLrUnhCibBNMVWt1EyFf2obqSl-4Qllv4_WRnCOE4HLrosIYQ"
    )
  ).map(setToken)

  val dataECDSA = Seq(
    DataEntry[JwtECDSAAlgorithm](
      JwtAlgorithm.ES256,
      """{"typ":"JWT","alg":"ES256"}""",
      JwtHeader(JwtAlgorithm.ES256, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9",
      "MIGIAkIBFmPwOO2eBdtkCko3pjjJs5Wpdi2GBhRywwptlosRQVlQxmT95uOoKE9BUjqVdyjd8o9TcNHHqM6ayPmQml0aTYICQgGDYkPc5EUfJ1F9VFvbPW1bIpX_sZ3XwyXIeL_4jt7BeKmB_LPorgeO-agmx4UdqMyCG1-Y31m8cJEPNm7h5x5V-Q"
    ),
    DataEntry[JwtECDSAAlgorithm](
      JwtAlgorithm.ES384,
      """{"typ":"JWT","alg":"ES384"}""",
      JwtHeader(JwtAlgorithm.ES384, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9",
      "MEUCIQC5trx72Z6QKUKxoK_DIX9S3X5QOJBu9tC3f5i6C_1gRQIgOYnA7NoLI3CNVLbibqAwQHSyU44f-yLYGn0YaJvReMA"
    ),
    DataEntry[JwtECDSAAlgorithm](
      JwtAlgorithm.ES512,
      """{"typ":"JWT","alg":"ES512"}""",
      JwtHeader(JwtAlgorithm.ES512, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9",
      "MEUCICcluU9j5N40Mcr_Mo5_r5KVexcgrXH0LMVC_k1EPswPAiEA-8W2vz2bVZCzPv-S6CNDlbxNktEkOtTAg0XXiZ0ghLk"
    )
  ).map(setToken)

  val dataEdDSA = Seq(
    DataEntry(
      JwtAlgorithm.EdDSA,
      """{"typ":"JWT","alg":"EdDSA"}""",
      JwtHeader(JwtAlgorithm.EdDSA, "JWT"),
      "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSJ9",
      "I4phqhsuywTyv0Fb12v0X-ILw8tFdDlDExRTsBUYMB2yjo340KXC8L_QfUyO7-8NoMzO5k4rHPkxq8cC2xu8CQ"
    )
  ).map(setToken)
}


================================================
FILE: core/shared/src/test/scala/JwtBase64Spec.scala
================================================
package pdi.jwt

class JwtBase64Spec extends munit.FunSuite {
  val eol = System.getProperty("line.separator")

  val values: Seq[(String, String)] = Seq(
    ("", ""),
    ("a", "YQ"),
    ("1", "MQ"),
    (
      """{"alg": "algo"}.{"user": 1, "admin": true, "value": "foo"}""",
      "eyJhbGciOiAiYWxnbyJ9LnsidXNlciI6IDEsICJhZG1pbiI6IHRydWUsICJ2YWx1ZSI6ICJmb28ifQ"
    ),
    (
      "azeklZJEKL,93l,zae:km838{az:e}lekr[l874:e]aze",
      "YXpla2xaSkVLTCw5M2wsemFlOmttODM4e2F6OmV9bGVrcltsODc0OmVdYXpl"
    ),
    (
      """azeqsdwxcrtyfghvbnuyiopjhkml1234567890&é'(-è_çà)=$£ù%*µ,?;.:/!+-*/§äâêëûüîïÂÄÊËÎÏÜÛÔÖZRTYPQSDFGHJKLMWXCVBN<>#{}[]|`\^@¤""",
      "YXplcXNkd3hjcnR5ZmdodmJudXlpb3BqaGttbDEyMzQ1Njc4OTAmw6knKC3DqF_Dp8OgKT0kwqPDuSUqwrUsPzsuOi8hKy0qL8Knw6TDosOqw6vDu8O8w67Dr8OCw4TDisOLw47Dj8Ocw5vDlMOWWlJUWVBRU0RGR0hKS0xNV1hDVkJOPD4je31bXXxgXF5AwqQ"
    )
  )

  test("should encode string") {
    values.foreach { value =>
      assertEquals(value._2, JwtBase64.encodeString(value._1))
    }
  }

  test("should decode strings") {
    values.foreach { value =>
      assertEquals(value._1, JwtBase64.decodeString(value._2))
    }
  }

  test("should be symmetrical") {
    values.foreach { value =>
      assertEquals(value._1, JwtBase64.decodeString(JwtBase64.encodeString(value._1)))
    }

    values.foreach { value =>
      assertEquals(value._2, JwtBase64.encodeString(JwtBase64.decodeString(value._2)))
    }
  }

  test("should throw when invalid string") {
    val vals = Seq("a", "abcde", "*", "aze$")
    vals.foreach { v =>
      intercept[IllegalArgumentException] { JwtBase64.decode(v) }
    }
  }
}


================================================
FILE: core/shared/src/test/scala/JwtClaimSpec.scala
================================================
package pdi.jwt

import java.time.{Clock, Instant, ZoneOffset}

import munit.ScalaCheckSuite
import org.scalacheck.Prop.*

class JwtClaimSpec extends ScalaCheckSuite {

  val fakeNowSeconds = 1615411490L
  implicit val clock: Clock = Clock.fixed(Instant.ofEpochSecond(fakeNowSeconds), ZoneOffset.UTC)
  val claim = JwtClaim()

  test("JwtClaim.+ should add a json") {
    forAll { (value: Long) =>
      val result = claim + s"""{"foo": $value}"""
      assertEquals(result.content, s"""{"foo": $value}""")
    }
  }

  test("JwtClaim.+ should add a key/value") {
    forAll { (value: Long) =>
      val result = claim + ("foo", value)
      assertEquals(result.content, s"""{"foo":$value}""")
    }
  }

  test("JwtClaim.++ should add a key/value") {
    forAll { (value: Long) =>
      val result = claim ++ ("foo" -> value)
      assertEquals(result.content, s"""{"foo":$value}""")
    }
  }

  test("JwtClaim.expireIn should set the expiration time") {
    forAll { (delta: Long) =>
      val result = claim.expiresIn(delta)
      assertEquals(result.expiration, Some(fakeNowSeconds + delta))
    }
  }

  test("JwtClaim.expireNow should set the expiration time") {
    val result = claim.expiresNow
    assertEquals(result.expiration, Some(fakeNowSeconds))
  }

  test("JwtClaim.expireAt should set the expiration time") {
    forAll { (epoch: Long) =>
      val result = claim.expiresAt(epoch)
      assertEquals(result.expiration, Some(epoch))
    }
  }

  test("JwtClaim.startIn should set the notBefore") {
    forAll { (delta: Long) =>
      val result = claim.startsIn(delta)
      assertEquals(result.notBefore, Some(fakeNowSeconds + delta))
    }
  }

  test("JwtClaim.startAt should set the notBefore") {
    forAll { (epoch: Long) =>
      val result = claim.startsAt(epoch)
      assertEquals(result.notBefore, Some(epoch))
    }
  }

  test("JwtClaim.startNow should set the notBefore") {
    val result = claim.startsNow
    assertEquals(result.notBefore, Some(fakeNowSeconds))
  }

  test("JwtClaim.issuedIn should set the issuedAt") {
    forAll { (delta: Long) =>
      val result = claim.issuedIn(delta)
      assertEquals(result.issuedAt, Some(fakeNowSeconds + delta))
    }
  }

  test("JwtClaim.issuedAt should set the issuedAt") {
    forAll { (epoch: Long) =>
      val result = claim.issuedAt(epoch)
      assertEquals(result.issuedAt, Some(epoch))
    }
  }

  test("JwtClaim.issuedNow should set the issuedAt") {
    val result = claim.issuedNow
    assertEquals(result.issuedAt, Some(fakeNowSeconds))
  }
}


================================================
FILE: docs/src/main/paradox/index.md
================================================
# JWT Scala

@@@ index

- [Native](jwt-core/index.md)
- [Argonaut](jwt-argonaut.md)
- [Circe](jwt-circe.md)
- [Json4S](jwt-json4s.md)
- [Play Json](jwt-play-json.md)
- [Play Framework](jwt-play-jwt-session.md)
- [upickle](jwt-upickle.md)
- [ZIO Json](jwt-zio-json.md)

@@@

Scala support for JSON Web Token ([JWT](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token)).
Supports Java 8+, Scala 2.12, Scala 2.13 and Scala 3 (for json libraries that support it).
Dependency free.
Optional helpers for Play Framework, Play JSON, Json4s Native, Json4s Jackson, Circe, uPickle and Argonaut.

## Usage

JWT Scala is divided in several sub-projects each targeting a specific JSON library,
check the doc from the menu for installation and usage instructions.

## Algorithms

If you are using `String` key, please keep in mind that such keys need to be parsed. Rather than implementing a super complex parser, the one in JWT Scala is pretty simple and might not work for all use-cases (especially for ECDSA keys). In such case, consider using `SecretKey` or `PrivateKey` or `PublicKey` directly. It is way better for you. All API support all those types.

Check @ref:[ECDSA samples](jwt-core/jwt-ecdsa.md) for more infos.

| Name  | Description                    |
| ----- | ------------------------------ |
| HMD5  | HMAC using MD5 algorithm       |
| HS224 | HMAC using SHA-224 algorithm   |
| HS256 | HMAC using SHA-256 algorithm   |
| HS384 | HMAC using SHA-384 algorithm   |
| HS512 | HMAC using SHA-512 algorithm   |
| RS256 | RSASSA using SHA-256 algorithm |
| RS384 | RSASSA using SHA-384 algorithm |
| RS512 | RSASSA using SHA-512 algorithm |
| ES256 | ECDSA using SHA-256 algorithm  |
| ES384 | ECDSA using SHA-384 algorithm  |
| ES512 | ECDSA using SHA-512 algorithm  |
| EdDSA | EdDSA signature algorithms     |

## Security concerns

This lib doesn't want to impose anything, that's why, by default, a JWT claim is totally empty. That said, you should always add an `issuedAt` attribute to it, probably using `claim.issuedNow`.
The reason is that even HTTPS isn't perfect and having always the same chunk of data transfered can be of a big help to crack it. Generating a slightly different token at each request is way better even if it adds a bit of payload to the response.
If you are using a session timeout through the `expiration` attribute which is extended at each request, that's fine too. I can't find the article I read about that vulnerability but if someone has some resources about the topic, I would be glad to link them.

## License

This software is licensed under the Apache 2 license, quoted below.

Copyright 2021 JWT-Scala Contributors.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this project except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.


================================================
FILE: docs/src/main/paradox/jwt-argonaut.md
================================================
## Argonaut

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtArgonaut$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-argonaut" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #example }

### Encoding

@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #encoding }

### Decoding

@@snip [JwtArgonautDoc.scala](/docs/src/main/scala/JwtArgonautDoc.scala) { #decoding }


================================================
FILE: docs/src/main/paradox/jwt-circe.md
================================================
## Circe

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtCirce$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-circe" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #example }

### Encoding

@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #encoding }

### Decoding

@@snip [JwtCirceDoc.scala](/docs/src/main/scala/JwtCirceDoc.scala) { #decoding }


================================================
FILE: docs/src/main/paradox/jwt-core/index.md
================================================
@@@ index

- [Claim](jwt-claim.md)
- [Claim Private](jwt-claim-private.md)
- [Header](jwt-header.md)
- [ECDSA](jwt-ecdsa.md)

@@@

This module doesn't use any dependency, it is useful if you don't have any Json library in your project.

It is based a naive parsing of Json strings, and doesn't support any custom parameter in the Claim so if you need any custom parameter, or if you're already using one of the supported Json libraries, consider using that instead.

## Native

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/Jwt$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-core" % "$project.version$"
```

@@@

### Basic usage

```scala mdoc:reset
import java.time.Clock
import pdi.jwt.{Jwt, JwtAlgorithm, JwtHeader, JwtClaim, JwtOptions}
implicit val clock: Clock = Clock.systemUTC
val token = Jwt.encode("""{"user":1}""", "secretKey", JwtAlgorithm.HS256)
Jwt.decodeRawAll(token, "secretKey", Seq(JwtAlgorithm.HS256))
Jwt.decodeRawAll(token, "wrongKey", Seq(JwtAlgorithm.HS256))
```

### Encoding

```scala mdoc
// Encode from string, header automatically generated
Jwt.encode("""{"user":1}""", "secretKey", JwtAlgorithm.HS384)

// Encode from case class, header automatically generated
// Set that the token has been issued now and expires in 10 seconds
Jwt.encode(JwtClaim({"""{"user":1}"""}).issuedNow.expiresIn(10), "secretKey", JwtAlgorithm.HS512)

// You can encode without signing it
Jwt.encode("""{"user":1}""")

// You can specify a string header but also need to specify the algorithm just to be sure
// This is not really typesafe, so please use it with care
Jwt.encode("""{"typ":"JWT","alg":"HS256"}""", """{"user":1}""", "key", JwtAlgorithm.HS256)

// If using a case class header, no need to repeat the algorithm
// This is way better than the previous one
Jwt.encode(JwtHeader(JwtAlgorithm.HS256), JwtClaim("""{"user":1}"""), "key")
```

### Decoding

In JWT Scala, espcially when using raw strings which are not typesafe at all, there are a lot of possible errors. This is why nearly all `decode` functions will return a `Try` rather than directly the expected result. In case of failure, the wrapped exception should tell you what went wrong.

Take note that nearly all decoding methods (including those from helper libs) support either a `String` key, or a `PrivateKey` with a Hmac algorithm or a `PublicKey` with a RSA or ECDSA algorithm.

```scala mdoc
// Decode all parts of the token as string
Jwt.decodeRawAll(token, "secretKey", JwtAlgorithm.allHmac())

// Decode only the claim as a string
Jwt.decodeRaw(token, "secretKey", Seq(JwtAlgorithm.HS256))

// Decode all parts and cast them as a better type if possible.
// Since the implementation in JWT Core only use string, it is the same as decodeRawAll
// But check the result in JWT Play JSON to see the difference
Jwt.decodeAll(token, "secretKey", Seq(JwtAlgorithm.HS256))

// Same as before, but only the claim
// (you should start to see a pattern in the naming convention of the functions)
Jwt.decode(token, "secretKey", Seq(JwtAlgorithm.HS256))

// Failure because the token is not a token at all
Jwt.decode("Hey there!")

// Failure if not Base64 encoded
Jwt.decode("a.b.c")

// Failure in case we use the wrong key
Jwt.decode(token, "wrongKey", Seq(JwtAlgorithm.HS256))

// Failure if the token only starts in 5 seconds
Jwt.decode(Jwt.encode(JwtClaim().startsIn(5)))
```

### Validating

If you only want to check if a token is valid without decoding it. You have two options: `validate` functions that will throw the exceptions we saw in the decoding section, so you know what went wrong, or `isValid` functions that will return a boolean in case you don't care about the actual error and don't want to bother with catching exception.

```scala mdoc:crash
// All good
Jwt.validate(token, "secretKey", Seq(JwtAlgorithm.HS256))
Jwt.isValid(token, "secretKey", Seq(JwtAlgorithm.HS256))

// Wrong key here
Jwt.validate(token, "wrongKey", Seq(JwtAlgorithm.HS256))
Jwt.isValid(token, "wrongKey", Seq(JwtAlgorithm.HS256))

// No key for unsigned token => ok
Jwt.validate(Jwt.encode("{}"))
Jwt.isValid(Jwt.encode("{}"))

// No key while the token is actually signed => wrong
Jwt.validate(token)
Jwt.isValid(token)

// The token hasn't started yet!
Jwt.validate(Jwt.encode(JwtClaim().startsIn(5)))
Jwt.isValid(Jwt.encode(JwtClaim().startsIn(5)))

// This is no token
Jwt.validate("a.b.c")
Jwt.isValid("a.b.c")
```

### Using a custom clock

For testing, it can sometimes be useful to use a fake clock that will always return a fixed time. It can be done by instanciating
`Jwt` instead of using the object (based on the system clock):

```scala mdoc
import java.time.{Clock, Instant, ZoneId}

val startTime = Clock.fixed(Instant.ofEpochSecond(0), ZoneId.of("UTC"))
val endTime = Clock.fixed(Instant.ofEpochSecond(5), ZoneId.of("UTC"))

val customJwt = Jwt(endTime)

val claim = JwtClaim().issuedNow(startTime).expiresIn(10)(startTime)
val encoded = customJwt.encode(claim, "key", JwtAlgorithm.HS256)

customJwt.decode(encoded, "key", JwtAlgorithm.allHmac())
```

### Options

All validating and decoding methods support a final optional argument as a `JwtOptions` which allow you to disable validation checks. This is useful if you need to access data from an expired token for example. You can disable `expiration`, `notBefore` and `signature` checks. Be warned that if you disable the last one, you have no guarantee that the user didn't change the content of the token.

```scala mdoc
val expiredToken = Jwt.encode(JwtClaim().by("me").expiresIn(-1))

// Fail since the token is expired
Jwt.isValid(expiredToken)
Jwt.decode(expiredToken)

// Let's disable expiration check
Jwt.isValid(expiredToken, JwtOptions(expiration = false))
Jwt.decode(expiredToken, JwtOptions(expiration = false))
```

You can also specify a leeway, in seconds, to account for clock skew.

```scala mdoc
// Allow 30sec leeway
Jwt.isValid(expiredToken, JwtOptions(leeway = 30))
Jwt.decode(expiredToken, JwtOptions(leeway = 30))
```


================================================
FILE: docs/src/main/paradox/jwt-core/jwt-claim-private.md
================================================
## Jwt Reserved Claims and Private Claims

A common use-case of Jwt-Scala (and JWT at large) is developing so-called "public" or "private" claims (and or header params).  These are functionally no different than "reserved" claims/header params, other than that they have no standard definition and may only be distinguished within your network or niche industry. "issuer", "subject", "audience" etc. are all examples of reserved claims, whereas "user" is a fairly common example of a non-reserved claim.

Given that there may be many of these public/private claims, rather than parsing them yourself separate from how reserved claims are parsed (see the *JwtClaim Class*), you can simply compose `JwtClaim` with your own custom claims that extend from the `JwtReservedClaim` trait.

Here is an example where reserved headers, along with a private "user" claim, is used:

```scala mdoc:reset
import pdi.jwt.{Jwt, JwtHeader, JwtClaim, JwtUtils, JwtJson4sParser}
import java.time.Clock

// define your network-specific claims, and compose them with the usual reservedClaims
case class JwtPrivateClaim(user: Option[String] = None, reservedClaims: JwtClaim = JwtClaim()) {
  // merge your json definition along with the reserved claims too
  def toJson: String = JwtUtils.mergeJson(JwtUtils.hashToJson(Seq(
      "user" -> user,
    ).collect {
      case (key, Some(value)) => (key -> value)
    }), reservedClaims.toJson)
}

// create a parser with claim type set to the one you just defined
// notice that the default `JwtHeader` class was used since we're only interested in overriding with a custom private claims type in this example
object JwtJson4sPrivate extends JwtJson4sParser[JwtHeader, JwtPrivateClaim] {
  override implicit val clock = Clock.systemUTC

  override protected def parseClaim(claim: String): JwtPrivateClaim = {
    val claimJson = super.parse(claim)
    val jwtReservedClaim: JwtClaim = super.readClaim(claimJson)
    val content = super.parse(jwtReservedClaim.content)
    JwtPrivateClaim(super.extractString(content, "user"), jwtReservedClaim.withContent("{}"))
  }

  // here is the only boilerplate (but if you chose to also specify a custom header type then you would make use of this)
  override protected def parseHeader(header: String): JwtHeader = super.readHeader(parse(header))

  // marginal boilerplate to ensure consistency with isValid checks now that your nesting reserved claims into your custom private claims
  override protected def extractExpiration(claim: JwtPrivateClaim): Option[Long] = claim.reservedClaims.expiration
  override protected def extractNotBefore(claim: JwtPrivateClaim): Option[Long] = claim.reservedClaims.notBefore
}
```

You can then use the same decodeAll method as you would before, now with your fully objectified claims:

```scala mdoc
import scala.util.Try
// this example chose to use JwtJson4s, but any Json implementation would work the same
val token: String = Jwt.encode("""{"user":"someone", "iss": "me"}""");
val decoded: Try[(JwtHeader, JwtPrivateClaim, String)] = JwtJson4sPrivate.decodeAll(token)
```


================================================
FILE: docs/src/main/paradox/jwt-core/jwt-claim.md
================================================
## JwtClaim Class

```scala mdoc:reset
import java.time.Clock
import pdi.jwt.JwtClaim

JwtClaim()

implicit val clock: Clock = Clock.systemUTC

// Specify the content as JSON string
// (don't use var in your code if possible, this is just to ease the sample)
var claim = JwtClaim("""{"user":1}""")

// Append new content
claim = claim + """{"key1":"value1"}"""
claim = claim + ("key2", true)
claim = claim ++ (("key3", 3), ("key4", Seq(1, 2)), ("key5", ("key5.1", "Subkey")))

// Stringify as JSON
claim.toJson

// Manipulate basic attributes
// Set the issuer
claim = claim.by("Me")

// Set the audience
claim = claim.to("You")

// Set the subject
claim = claim.about("Something")

// Set the id
claim = claim.withId("42")

// Set the expiration
// In 10 seconds from now
claim = claim.expiresIn(5)
// At a specific timestamp (in seconds)
claim.expiresAt(1431520421)
// Right now! (the token is directly invalid...)
claim.expiresNow

// Set the beginning of the token (aka the "not before" attribute)
// 5 seconds ago
claim.startsIn(-5)
// At a specific timestamp (in seconds)
claim.startsAt(1431520421)
// Right now!
claim = claim.startsNow

// Set the date when the token was created
// (you should always use claim.issuedNow, but I let you do otherwise if needed)
// 5 seconds ago
claim.issuedIn(-5)
// At a specific timestamp (in seconds)
claim.issuedAt(1431520421)
// Right now!
claim = claim.issuedNow

// We can test if the claim is valid => testing if the current time is between "not before" and "expiration"
claim.isValid

// Also test the issuer and audience
claim.isValid("Me", "You")

// Let's stringify the final version
claim.toJson
```


================================================
FILE: docs/src/main/paradox/jwt-core/jwt-ecdsa.md
================================================
## Jwt with ECDSA algorithms

### With generated keys

#### Generation

```scala
import org.bouncycastle.jce.provider.BouncyCastleProvider
import java.security.spec.{ECPrivateKeySpec, ECPublicKeySpec, ECGenParameterSpec, ECParameterSpec, ECPoint}
import java.security.{SecureRandom, KeyFactory, KeyPairGenerator, Security}
import pdi.jwt.{Jwt, JwtAlgorithm}
// We specify the curve we want to use
val ecGenSpec = new ECGenParameterSpec("P-521")
// We are going to use a ECDSA algorithm
// and the Bouncy Castle provider
if (Security.getProvider("BC") == null) {
  Security.addProvider(new BouncyCastleProvider())
}
val generatorEC = KeyPairGenerator.getInstance("ECDSA", "BC")
generatorEC.initialize(ecGenSpec, new SecureRandom())
// Generate a pair of keys, one private for encoding
// and one public for decoding
val ecKey = generatorEC.generateKeyPair()
```

#### Usage

```scala
val token = Jwt.encode("""{"user":1}""", ecKey.getPrivate, JwtAlgorithm.ES512)

Jwt.decode(token, ecKey.getPublic, JwtAlgorithm.allECDSA)
```

### With saved keys

Let's say you already have your keys, it means you know the **S** param for the private key and both **(X, Y)** for the public key. So we will first recreate the keys from those params and then use them just as we did for the previously generated keys.

#### Creation

```scala
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.spec.ECNamedCurveSpec

// Our saved params
val S = BigInt("1ed498eedf499e5dd12b1ab94ee03d1a722eaca3ed890630c8b25f1015dd4ec5630a02ddb603f3248a3b87c88637e147ecc7a6e2a1c2f9ff1103be74e5d42def37d", 16)
val X = BigInt("16528ac15dc4c8e0559fad628ac3ffbf5c7cfefe12d50a97c7d088cc10b408d4ab03ac0d543bde862699a74925c1f2fe7c247c00fddc1442099dfa0671fc032e10a", 16)
val Y = BigInt("b7f22b3c1322beef766cadd1a5f0363840195b7be10d9a518802d8d528e03bc164c9588c5e63f1473d05195510676008b6808508539367d2893e1aa4b7cb9f9dab", 16)

// Here we are using the P-521 curve but you need to change it
// to your own curve
val curveParams = ECNamedCurveTable.getParameterSpec("P-521")
val curveSpec: ECParameterSpec = new ECNamedCurveSpec( "P-521", curveParams.getCurve(), curveParams.getG(), curveParams.getN(), curveParams.getH());

val privateSpec = new ECPrivateKeySpec(S.underlying(), curveSpec)
val publicSpec = new ECPublicKeySpec(new ECPoint(X.underlying(), Y.underlying()), curveSpec)

val privateKeyEC = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(privateSpec)
val publicKeyEC = KeyFactory.getInstance("ECDSA", "BC").generatePublic(publicSpec)
```

#### Usage

```scala
val token = Jwt.encode("""{"user":1}""", privateKeyEC, JwtAlgorithm.ES512)

Jwt.decode(token, publicKeyEC, Seq(JwtAlgorithm.ES512))

// Wrong key...
Jwt.decode(token, ecKey.getPublic, Seq(JwtAlgorithm.ES512))
```


================================================
FILE: docs/src/main/paradox/jwt-core/jwt-header.md
================================================
## JwtHeader Case Class

```scala mdoc:reset
import pdi.jwt.{JwtHeader, JwtAlgorithm}

JwtHeader()
JwtHeader(JwtAlgorithm.HS256)
JwtHeader(JwtAlgorithm.HS256, "JWT")

// You can stringify it to JSON
JwtHeader(JwtAlgorithm.HS256, "JWT").toJson

// You can assign the default type (but it would have be done automatically anyway)
JwtHeader(JwtAlgorithm.HS256).withType
```


================================================
FILE: docs/src/main/paradox/jwt-json4s.md
================================================
## Json4s

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson4s$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-json4s" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #example }

### Encoding

@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #encode }

### Decoding

@@snip [JwtJson4sDoc.scala](/docs/src/main/scala/JwtJson4sDoc.scala) { #decode }


================================================
FILE: docs/src/main/paradox/jwt-play-json.md
================================================
## Play Json

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-play-json" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #example }

### Encoding

@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #encode }

### Decoding

@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #decode }

### Formating

The project provides implicit reader and writer for both `JwtHeader` and `JwtClaim`

@@snip [JwtPlayJsonDoc.scala](/docs/src/main/scala/JwtPlayJsonDoc.scala) { #format }


================================================
FILE: docs/src/main/paradox/jwt-play-jwt-session.md
================================================
## JwtSession case class

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-play" % "$project.version$"
```

@@@

Provides an API similar to the Play [Session](https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.mvc.Session) but using `JsValue` rather than `String` as values. It also separates `headerData` from `claimData` rather than having only one `data`.

### Basic usage

@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #example }

### Using implicits

If you have implicit `Reads` and/or `Writes`, you can access and/or add data directly as case class or object.

@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #implicits }

## Play RequestHeader

You can extract a `JwtSession` from a `RequestHeader`.

@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #requestheader }

## Play Result

There are also implicit helpers around `Result` to help you manipulate the session inside it.

@@snip [JwtPlayJwtSessionDoc.scala](/docs/src/main/scala/JwtPlayJwtSessionDoc.scala) { #result }

## Play configuration

### Secret key

`play.http.secret.key`

> Default: none

The secret key is used to secure cryptographics functions. We are using the same key to sign Json Web Tokens so you don't need to worry about it.

### Private key

`play.http.session.privateKey`

> Default: none

The PKCS8 format private key is used to sign JWT session. If `play.http.session.privateKey` is missing `play.http.secret.key` used instead.

### Public key

`play.http.session.publicKey`

> Default: none

The X.509 format public key is used to verify JWT session signed with private key `play.http.session.privateKey`

### Session timeout

`play.http.session.maxAge`

> Default: none

Just like for the cookie session, you can use this key to specify the duration, in milliseconds or using the duration syntax (for example 30m or 1h), after which the user should be logout, which mean the token will no longer be valid. It means you need to refresh the expiration date at each request

### Signature algorithm

`play.http.session.algorithm`

> Default: HS256
>
> Supported: HMD5, HS1, HS224, HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512

You can specify which algorithm you want to use, among the supported ones, in order to create the signature which will assure you that nobody can actually change the token. You should probably stick with the default one or use HmacSHA512 for maximum security.

### Header name

`play.http.session.jwtName`

> Default: Authorization

You can change the name of the header in which the token should be stored. It will be used for both requests and responses.

### Response header name

`play.http.session.jwtResponseName`

> Default: none

If you need to have a different header for request and response, you can override the response header using this key.

### Token prefix

`play.http.session.tokenPrefix`

> Default: "Bearer "

Authorization header should have a prefix before the token, like "Basic" for example. For a JWT token, it should be "Bearer" (which is the default value) but you can freely change or remove it (using an empty string). The token prefix will be directly prepend before the token, so be sure to put any necessary whitespaces in it.


================================================
FILE: docs/src/main/paradox/jwt-upickle.md
================================================
## upickle

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtUpickle$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-upickle" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #example }

### Encoding

@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #encoding }

### Decoding

@@snip [JwtUpickleDoc.scala](/docs/src/main/scala/JwtUpickleDoc.scala) { #decoding }


================================================
FILE: docs/src/main/paradox/jwt-zio-json.md
================================================
## ZIO Json

- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtZioJson$.html)

@@@vars

```scala
libraryDependencies += "com.github.jwt-scala" %% "jwt-zio-json" % "$project.version$"
```

@@@

### Basic usage

@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #example }

### Encoding

@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #encoding }

### Decoding

@@snip [JwtZioDoc.scala](/docs/src/main/scala/JwtZioDoc.scala) { #decoding }


================================================
FILE: docs/src/main/paradox/project/build.properties
================================================
sbt.version=1.5.0


================================================
FILE: docs/src/main/scala/JwtArgonautDoc.scala
================================================
package pdi.jwt.docs

object JwtArgonautDoc {

  // #example
  import java.time.Instant
  import scala.util.Try

  import argonaut.Json
  import pdi.jwt.{JwtAlgorithm, JwtArgonaut, JwtClaim}

  val claim = JwtClaim(
    expiration = Some(Instant.now().plusSeconds(157784760).getEpochSecond),
    issuedAt = Some(Instant.now.getEpochSecond)
  )

  val key = "secretKey"
  val alg = JwtAlgorithm.HS512

  val token = JwtArgonaut.encode(claim, key, alg)
  val decodedJson: Try[Json] = JwtArgonaut.decodeJson(token, key, Seq(alg))
  val decodedClaim: Try[JwtClaim] = JwtArgonaut.decode(token, key, Seq(alg))
  // #example
}

object JwtArgonautDocEncoding {
  // #encoding
  import java.time.Instant

  import argonaut.Parse
  import pdi.jwt.{JwtAlgorithm, JwtArgonaut}

  val key = "secretKey"
  val alg = JwtAlgorithm.HS512

  val jsonClaim = Parse.parseOption(s"""{"expires":${Instant.now().getEpochSecond}}""").get
  val jsonHeader = Parse.parseOption("""{"typ":"JWT","alg":"HS512"}""").get

  val token1: String = JwtArgonaut.encode(jsonClaim)
  val token2: String = JwtArgonaut.encode(jsonClaim, key, alg)
  val token3: String = JwtArgonaut.encode(jsonHeader, jsonClaim, key)
  // #encoding
}

object JwtArgonautDocDecoding {
  import java.time.Instant
  // #decoding
  import scala.util.Try

  import argonaut.Json
  import pdi.jwt.{JwtAlgorithm, JwtArgonaut, JwtClaim, JwtHeader}

  val claim = JwtClaim(
    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),
    issuedAt = Some(Instant.now.getEpochSecond)
  )
  val key = "secretKey"
  val alg = JwtAlgorithm.HS512

  val token = JwtArgonaut.encode(claim, key, alg)

  val decodedJsonClaim: Try[Json] = JwtArgonaut.decodeJson(token, key, Seq(alg))
  val decodedJson: Try[(Json, Json, String)] = JwtArgonaut.decodeJsonAll(token, key, Seq(alg))

  val decodedClaim: Try[JwtClaim] = JwtArgonaut.decode(token, key, Seq(alg))
  val decodedToken: Try[(JwtHeader, JwtClaim, String)] = JwtArgonaut.decodeAll(token, key, Seq(alg))
  // #decoding
}


================================================
FILE: docs/src/main/scala/JwtCirceDoc.scala
================================================
package pdi.jwt.docs

import scala.annotation.nowarn

@nowarn
object CirceExample {

  // #example
  import java.time.Instant

  import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim}

  val claim = JwtClaim(
    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),
    issuedAt = Some(Instant.now.getEpochSecond)
  )
  val key = "secretKey"
  val algo = JwtAlgorithm.HS256

  val token = JwtCirce.encode(claim, key, algo)

  JwtCirce.decodeJson(token, key, Seq(JwtAlgorithm.HS256))
  JwtCirce.decode(token, key, Seq(JwtAlgorithm.HS256))
  // #example
}

@nowarn
object CirceEncoding {
  // #encoding
  import java.time.Instant

  import io.circe._
  import jawn.{parse => jawnParse}
  import pdi.jwt.{JwtAlgorithm, JwtCirce}

  val key = "secretKey"
  val algo = JwtAlgorithm.HS256

  val Right(claimJson) = jawnParse(s"""{"expires":${Instant.now.getEpochSecond}}""")
  val Right(header) = jawnParse("""{"typ":"JWT","alg":"HS256"}""")
  // From just the claim to all possible attributes
  JwtCirce.encode(claimJson)
  JwtCirce.encode(claimJson, key, algo)
  JwtCirce.encode(header, claimJson, key)
  // #encoding
}

@nowarn
object CirceDecoding {
  // #decoding
  import java.time.Instant

  import pdi.jwt.{JwtAlgorithm, JwtCirce, JwtClaim}

  val claim = JwtClaim(
    expiration = Some(Instant.now.plusSeconds(157784760).getEpochSecond),
    issuedAt = Some(Instant.now.getEpochSecond)
  )
  val key = "secretKey"
  val algo = JwtAlgorithm.HS256

  val token = JwtCirce.encode(claim, key, algo)

  // You can decode to JsObject
  JwtCirce.decodeJson(token, key, Seq(JwtAlgorithm.HS256))
  JwtCirce.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))
  // Or to case classes
  JwtCirce.decode(token, key, Seq(JwtAlgorithm.HS256))
  JwtCirce.decodeAll(token, key, Seq(JwtAlgorithm.HS256))
  // #decoding
}


================================================
FILE: docs/src/main/scala/JwtJson4sDoc.scala
================================================
package pdi.jwt.docs

import scala.annotation.nowarn

@nowarn
object JwtJson4sDoc {
  // #example
  import org.json4s.JsonDSL.WithBigDecimal._
  import org.json4s._
  import pdi.jwt.{JwtAlgorithm, JwtJson4s}

  val claim = JObject(("user", 1), ("nbf", 1431520421))
  val key = "secretKey"
  val algo = JwtAlgorithm.HS256

  JwtJson4s.encode(claim)

  val token = JwtJson4s.encode(claim, key, algo)

  JwtJson4s.decodeJson(token, key, Seq(JwtAlgorithm.HS256))

  JwtJson4s.decode(token, key, Seq(JwtAlgorithm.HS256))
  // #example

  // #encode
  val header = JObject(("typ", "JWT"), ("alg", "HS256"))

  JwtJson4s.encode(claim)
  JwtJson4s.encode(claim, key, algo)
  JwtJson4s.encode(header, claim, key)
  // #encode

  // #decode
  // You can decode to JsObject
  JwtJson4s.decodeJson(token, key, Seq(JwtAlgorithm.HS256))
  JwtJson4s.decodeJsonAll(token, key, Seq(JwtAlgorithm.HS256))
  // Or to case classes
  JwtJson4s.decode(token, key, Seq(JwtAlgorithm.HS256))
  JwtJson4s.decodeAll(token, key, Seq(JwtAlgorithm.HS256))
  // #decode
}


================================================
FILE: docs/src/main/scala/JwtPlayJsonDoc.scala
================================================
package pdi.jwt.docs

import scala.annotation.nowarn

@nowarn
object JwtPlayJsonDoc {
  // #example
  import java.time.Clock

  import pdi.jwt._
  import play.api.libs.js
Download .txt
gitextract_3pvve9sy/

├── .git-blame-ignore-revs
├── .github/
│   └── workflows/
│       ├── docs.yml
│       ├── release.yml
│       └── tests.yml
├── .gitignore
├── .scala-steward.conf
├── .scalafmt.conf
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sbt
├── core/
│   ├── jvm/
│   │   └── src/
│   │       └── test/
│   │           └── scala/
│   │               ├── JwtSpec.scala
│   │               └── JwtUtilsSpec.scala
│   └── shared/
│       └── src/
│           ├── main/
│           │   └── scala/
│           │       ├── Jwt.scala
│           │       ├── JwtAlgorithm.scala
│           │       ├── JwtArrayUtils.scala
│           │       ├── JwtBase64.scala
│           │       ├── JwtClaim.scala
│           │       ├── JwtCore.scala
│           │       ├── JwtException.scala
│           │       ├── JwtHeader.scala
│           │       ├── JwtOptions.scala
│           │       ├── JwtTime.scala
│           │       └── JwtUtils.scala
│           └── test/
│               └── scala/
│                   ├── Fixture.scala
│                   ├── JwtBase64Spec.scala
│                   └── JwtClaimSpec.scala
├── docs/
│   └── src/
│       └── main/
│           ├── paradox/
│           │   ├── index.md
│           │   ├── jwt-argonaut.md
│           │   ├── jwt-circe.md
│           │   ├── jwt-core/
│           │   │   ├── index.md
│           │   │   ├── jwt-claim-private.md
│           │   │   ├── jwt-claim.md
│           │   │   ├── jwt-ecdsa.md
│           │   │   └── jwt-header.md
│           │   ├── jwt-json4s.md
│           │   ├── jwt-play-json.md
│           │   ├── jwt-play-jwt-session.md
│           │   ├── jwt-upickle.md
│           │   ├── jwt-zio-json.md
│           │   └── project/
│           │       └── build.properties
│           └── scala/
│               ├── JwtArgonautDoc.scala
│               ├── JwtCirceDoc.scala
│               ├── JwtJson4sDoc.scala
│               ├── JwtPlayJsonDoc.scala
│               ├── JwtPlayJwtSessionDoc.scala
│               ├── JwtUpickleDoc.scala
│               └── JwtZioDoc.scala
├── json/
│   ├── argonaut/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtArgonaut.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── ArgonautFixture.scala
│   │               └── JwtArgonautSpec.scala
│   ├── circe/
│   │   ├── jvm/
│   │   │   └── src/
│   │   │       └── test/
│   │   │           └── scala/
│   │   │               ├── CirceFixture.scala
│   │   │               └── JwtCirceSpec.scala
│   │   └── shared/
│   │       └── src/
│   │           └── main/
│   │               └── scala/
│   │                   └── JwtCirce.scala
│   ├── common/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtJsonCommon.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── JsonCommonFixture.scala
│   │               └── JwtJsonCommonSpec.scala
│   ├── json4s-common/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       └── JwtJson4sCommon.scala
│   │       └── test/
│   │           └── scala/
│   │               └── Json4sCommonFixture.scala
│   ├── json4s-jackson/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson4sImplicits.scala
│   │       │       └── JwtJson4sJackson.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── Json4sJacksonFixture.scala
│   │               └── Json4sJacksonSpec.scala
│   ├── json4s-native/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson4sImplicits.scala
│   │       │       └── JwtJson4sNative.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── Json4sNativeFixture.scala
│   │               └── Json4sNativeSpec.scala
│   ├── play-json/
│   │   └── src/
│   │       ├── main/
│   │       │   └── scala/
│   │       │       ├── JwtJson.scala
│   │       │       └── JwtJsonImplicits.scala
│   │       └── test/
│   │           └── scala/
│   │               ├── JsonFixture.scala
│   │               └── JwtJsonSpec.scala
│   ├── upickle/
│   │   ├── jvm/
│   │   │   └── src/
│   │   │       └── test/
│   │   │           └── scala/
│   │   │               ├── JwtUpickleFixture.scala
│   │   │               └── JwtUpickleSpec.scala
│   │   └── shared/
│   │       └── src/
│   │           └── main/
│   │               └── scala/
│   │                   ├── JwtUpickle.scala
│   │                   └── JwtUpickleImplicits.scala
│   └── zio-json/
│       └── src/
│           ├── main/
│           │   └── scala/
│           │       └── JwtZIOJson.scala
│           └── test/
│               └── scala/
│                   ├── JwtZIOJsonSpec.scala
│                   └── ZIOJsonFixture.scala
├── play/
│   └── src/
│       ├── main/
│       │   └── scala/
│       │       ├── JwtPlayImplicits.scala
│       │       └── JwtSession.scala
│       └── test/
│           └── scala/
│               ├── JwtResultSpec.scala
│               ├── JwtSessionAsymetricSpec.scala
│               ├── JwtSessionCustomDifferentNameSpec.scala
│               ├── JwtSessionCustomSpec.scala
│               ├── JwtSessionSpec.scala
│               └── PlayFixture.scala
├── project/
│   ├── Libs.scala
│   ├── build.properties
│   └── plugins.sbt
└── scripts/
    ├── bump.sh
    ├── clean.sh
    └── pu.sh
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (320K chars).
[
  {
    "path": ".git-blame-ignore-revs",
    "chars": 352,
    "preview": "# Scala Steward: Reformat with scalafmt 3.7.2\ne8c410d04442fe9ac7aa50df34a398972a602cdc\n\n# Scala Steward: Reformat with s"
  },
  {
    "path": ".github/workflows/docs.yml",
    "chars": 1012,
    "preview": "name: Docs\non:\n  push:\n    tags: [\"*\"]\nconcurrency:\n  group: docs\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 628,
    "preview": "name: Release\non:\n  push:\n    branches: [master, main]\n    tags: [\"*\"]\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    s"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 2454,
    "preview": "name: CI\n\non:\n  pull_request:\n    branches: [master, main]\n  push:\n    branches: [master, main]\n\nenv:\n  SCALA212: 2.12.2"
  },
  {
    "path": ".gitignore",
    "chars": 94,
    "preview": ".history\ntarget\nproject/project\nproject/target\n.bloop/\n.idea\n.bsp\n.metals/\n.vscode/\nmetals.sbt"
  },
  {
    "path": ".scala-steward.conf",
    "chars": 79,
    "preview": "updates.ignore = [\n  { groupId = \"com.google.inject\", artifactId = \"guice\" }\n]\n"
  },
  {
    "path": ".scalafmt.conf",
    "chars": 229,
    "preview": "version=3.10.7\nrunner.dialect=scala213\n\nmaxColumn = 100\n\nrewrite.rules = [Imports, AvoidInfix, SortModifiers, PreferCurl"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 12951,
    "preview": "# Changelog\n\nNote: this file is no longer updated, check the [releases tab](https://github.com/jwt-scala/jwt-scala/relea"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1493,
    "preview": "## Contributor Guide\n\nFor any bug, new feature, or documentation improvement,\nthe best way to start a conversation is by"
  },
  {
    "path": "LICENSE",
    "chars": 10174,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "README.md",
    "chars": 3204,
    "preview": "# JWT Scala\n\nScala support for JSON Web Token ([JWT](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token)).\nSuppo"
  },
  {
    "path": "build.sbt",
    "chars": 9232,
    "preview": "import scala.io.Source\nimport scala.sys.process._\n\nimport com.jsuereth.sbtpgp.PgpKeys._\nimport sbt.Keys._\nimport sbt.Tes"
  },
  {
    "path": "core/jvm/src/test/scala/JwtSpec.scala",
    "chars": 20443,
    "preview": "package pdi.jwt\n\nimport scala.util.{Success, Try}\n\nimport pdi.jwt.algorithms.*\nimport pdi.jwt.exceptions.*\n\nclass JwtSpe"
  },
  {
    "path": "core/jvm/src/test/scala/JwtUtilsSpec.scala",
    "chars": 7869,
    "preview": "package pdi.jwt\n\nimport java.security.spec.ECGenParameterSpec\nimport java.security.{KeyPairGenerator, SecureRandom}\n\nimp"
  },
  {
    "path": "core/shared/src/main/scala/Jwt.scala",
    "chars": 4366,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.matching.Regex\n\n/** Test implementation of [[JwtCore]] using o"
  },
  {
    "path": "core/shared/src/main/scala/JwtAlgorithm.scala",
    "chars": 4133,
    "preview": "package pdi.jwt\n\nimport scala.annotation.nowarn\n\nimport pdi.jwt.algorithms.JwtUnknownAlgorithm\n\nsealed trait JwtAlgorith"
  },
  {
    "path": "core/shared/src/main/scala/JwtArrayUtils.scala",
    "chars": 1029,
    "preview": "package pdi.jwt\n\nobject JwtArrayUtils {\n\n  /** A constant time equals comparison - does not terminate early if test will"
  },
  {
    "path": "core/shared/src/main/scala/JwtBase64.scala",
    "chars": 1131,
    "preview": "package pdi.jwt\n\nobject JwtBase64 {\n  private lazy val encoder = java.util.Base64.getUrlEncoder()\n  private lazy val dec"
  },
  {
    "path": "core/shared/src/main/scala/JwtClaim.scala",
    "chars": 5250,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nobject JwtClaim {\n  def apply(\n      content: String = \"{}\",\n      issuer: Opti"
  },
  {
    "path": "core/shared/src/main/scala/JwtCore.scala",
    "chars": 42392,
    "preview": "package pdi.jwt\n\nimport java.security.{Key, PrivateKey, PublicKey}\nimport java.time.Clock\nimport javax.crypto.SecretKey\n"
  },
  {
    "path": "core/shared/src/main/scala/JwtException.scala",
    "chars": 2328,
    "preview": "package pdi.jwt.exceptions\n\nimport pdi.jwt.JwtTime\n\nsealed abstract class JwtException(message: String) extends RuntimeE"
  },
  {
    "path": "core/shared/src/main/scala/JwtHeader.scala",
    "chars": 2506,
    "preview": "package pdi.jwt\n\nobject JwtHeader {\n  val DEFAULT_TYPE = \"JWT\"\n\n  def apply(\n      algorithm: Option[JwtAlgorithm] = Non"
  },
  {
    "path": "core/shared/src/main/scala/JwtOptions.scala",
    "chars": 227,
    "preview": "package pdi.jwt\n\ncase class JwtOptions(\n    signature: Boolean = true,\n    expiration: Boolean = true,\n    notBefore: Bo"
  },
  {
    "path": "core/shared/src/main/scala/JwtTime.scala",
    "chars": 2914,
    "preview": "package pdi.jwt\n\nimport java.time.{Clock, Instant}\nimport scala.util.{Failure, Success, Try}\n\nimport pdi.jwt.exceptions."
  },
  {
    "path": "core/shared/src/main/scala/JwtUtils.scala",
    "chars": 12552,
    "preview": "package pdi.jwt\n\nimport java.security.spec.{PKCS8EncodedKeySpec, X509EncodedKeySpec}\nimport java.security.{KeyFactory, P"
  },
  {
    "path": "core/shared/src/test/scala/Fixture.scala",
    "chars": 11475,
    "preview": "package pdi.jwt\n\nimport java.security.spec.*\nimport java.security.{KeyFactory, KeyPairGenerator, SecureRandom, Security}"
  },
  {
    "path": "core/shared/src/test/scala/JwtBase64Spec.scala",
    "chars": 1620,
    "preview": "package pdi.jwt\n\nclass JwtBase64Spec extends munit.FunSuite {\n  val eol = System.getProperty(\"line.separator\")\n\n  val va"
  },
  {
    "path": "core/shared/src/test/scala/JwtClaimSpec.scala",
    "chars": 2538,
    "preview": "package pdi.jwt\n\nimport java.time.{Clock, Instant, ZoneOffset}\n\nimport munit.ScalaCheckSuite\nimport org.scalacheck.Prop."
  },
  {
    "path": "docs/src/main/paradox/index.md",
    "chars": 3238,
    "preview": "# JWT Scala\n\n@@@ index\n\n- [Native](jwt-core/index.md)\n- [Argonaut](jwt-argonaut.md)\n- [Circe](jwt-circe.md)\n- [Json4S](j"
  },
  {
    "path": "docs/src/main/paradox/jwt-argonaut.md",
    "chars": 526,
    "preview": "## Argonaut\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtArgonaut$.html)\n\n@@@vars\n\n```sca"
  },
  {
    "path": "docs/src/main/paradox/jwt-circe.md",
    "chars": 499,
    "preview": "## Circe\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtCirce$.html)\n\n@@@vars\n\n```scala\nlib"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/index.md",
    "chars": 6042,
    "preview": "@@@ index\n\n- [Claim](jwt-claim.md)\n- [Claim Private](jwt-claim-private.md)\n- [Header](jwt-header.md)\n- [ECDSA](jwt-ecdsa"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-claim-private.md",
    "chars": 3081,
    "preview": "## Jwt Reserved Claims and Private Claims\n\nA common use-case of Jwt-Scala (and JWT at large) is developing so-called \"pu"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-claim.md",
    "chars": 1653,
    "preview": "## JwtClaim Class\n\n```scala mdoc:reset\nimport java.time.Clock\nimport pdi.jwt.JwtClaim\n\nJwtClaim()\n\nimplicit val clock: C"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-ecdsa.md",
    "chars": 2768,
    "preview": "## Jwt with ECDSA algorithms\n\n### With generated keys\n\n#### Generation\n\n```scala\nimport org.bouncycastle.jce.provider.Bo"
  },
  {
    "path": "docs/src/main/paradox/jwt-core/jwt-header.md",
    "chars": 371,
    "preview": "## JwtHeader Case Class\n\n```scala mdoc:reset\nimport pdi.jwt.{JwtHeader, JwtAlgorithm}\n\nJwtHeader()\nJwtHeader(JwtAlgorith"
  },
  {
    "path": "docs/src/main/paradox/jwt-json4s.md",
    "chars": 504,
    "preview": "## Json4s\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson4s$.html)\n\n@@@vars\n\n```scala\nl"
  },
  {
    "path": "docs/src/main/paradox/jwt-play-json.md",
    "chars": 706,
    "preview": "## Play Json\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtJson$.html)\n\n@@@vars\n\n```scala\n"
  },
  {
    "path": "docs/src/main/paradox/jwt-play-jwt-session.md",
    "chars": 3362,
    "preview": "## JwtSession case class\n\n@@@vars\n\n```scala\nlibraryDependencies += \"com.github.jwt-scala\" %% \"jwt-play\" % \"$project.vers"
  },
  {
    "path": "docs/src/main/paradox/jwt-upickle.md",
    "chars": 517,
    "preview": "## upickle\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtUpickle$.html)\n\n@@@vars\n\n```scala"
  },
  {
    "path": "docs/src/main/paradox/jwt-zio-json.md",
    "chars": 495,
    "preview": "## ZIO Json\n\n- [API Documentation](https://jwt-scala.github.io/jwt-scala/api/pdi/jwt/JwtZioJson$.html)\n\n@@@vars\n\n```scal"
  },
  {
    "path": "docs/src/main/paradox/project/build.properties",
    "chars": 18,
    "preview": "sbt.version=1.5.0\n"
  },
  {
    "path": "docs/src/main/scala/JwtArgonautDoc.scala",
    "chars": 2013,
    "preview": "package pdi.jwt.docs\n\nobject JwtArgonautDoc {\n\n  // #example\n  import java.time.Instant\n  import scala.util.Try\n\n  impor"
  },
  {
    "path": "docs/src/main/scala/JwtCirceDoc.scala",
    "chars": 1819,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject CirceExample {\n\n  // #example\n  import java.time.In"
  },
  {
    "path": "docs/src/main/scala/JwtJson4sDoc.scala",
    "chars": 1040,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtJson4sDoc {\n  // #example\n  import org.json4s.Js"
  },
  {
    "path": "docs/src/main/scala/JwtPlayJsonDoc.scala",
    "chars": 1505,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtPlayJsonDoc {\n  // #example\n  import java.time.C"
  },
  {
    "path": "docs/src/main/scala/JwtPlayJwtSessionDoc.scala",
    "chars": 3685,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtPlayJwtSessionDoc {\n  // #example\n  import java."
  },
  {
    "path": "docs/src/main/scala/JwtUpickleDoc.scala",
    "chars": 1822,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtUpickleDoc {\n  // #example\n  import java.time.In"
  },
  {
    "path": "docs/src/main/scala/JwtZioDoc.scala",
    "chars": 1916,
    "preview": "package pdi.jwt.docs\n\nimport scala.annotation.nowarn\n\n@nowarn\nobject JwtZioDoc {\n  // #example\n  import java.time.Instan"
  },
  {
    "path": "json/argonaut/src/main/scala/JwtArgonaut.scala",
    "chars": 2722,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport argonaut.*\nimport argonaut.Argonaut.*\n\ntrait JwtArgonautParser[H, C] ext"
  },
  {
    "path": "json/argonaut/src/test/scala/ArgonautFixture.scala",
    "chars": 804,
    "preview": "package pdi.jwt\n\nimport argonaut.*\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClas"
  },
  {
    "path": "json/argonaut/src/test/scala/JwtArgonautSpec.scala",
    "chars": 256,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport argonaut.Json\n\nclass JwtArgonautSpec extends JwtJsonCommonSpec[Json] wit"
  },
  {
    "path": "json/circe/jvm/src/test/scala/CirceFixture.scala",
    "chars": 919,
    "preview": "package pdi.jwt\n\nimport io.circe.*\nimport io.circe.jawn.{parse => jawnParse}\n\ncase class JsonDataEntry(\n    algo: JwtAlg"
  },
  {
    "path": "json/circe/jvm/src/test/scala/JwtCirceSpec.scala",
    "chars": 204,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport io.circe.*\n\nclass JwtCirceSpec extends JwtJsonCommonSpec[Json] with Circ"
  },
  {
    "path": "json/circe/shared/src/main/scala/JwtCirce.scala",
    "chars": 2233,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport io.circe.*\nimport io.circe.jawn.{parse => jawnParse}\nimport io.circe.syn"
  },
  {
    "path": "json/common/src/main/scala/JwtJsonCommon.scala",
    "chars": 7112,
    "preview": "package pdi.jwt\n\nimport java.security.{Key, PrivateKey, PublicKey}\nimport javax.crypto.SecretKey\nimport scala.util.Try\n\n"
  },
  {
    "path": "json/common/src/test/scala/JsonCommonFixture.scala",
    "chars": 363,
    "preview": "package pdi.jwt\n\ntrait JsonDataEntryTrait[J] extends DataEntryBase {\n  def headerJson: J\n}\n\ntrait JsonCommonFixture[J] e"
  },
  {
    "path": "json/common/src/test/scala/JwtJsonCommonSpec.scala",
    "chars": 5282,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.Success\n\nabstract class JwtJsonCommonSpec[J] extends munit.Fun"
  },
  {
    "path": "json/json4s-common/src/main/scala/JwtJson4sCommon.scala",
    "chars": 3472,
    "preview": "package pdi.jwt\n\nimport org.json4s.*\nimport pdi.jwt.exceptions.{\n  JwtNonNumberException,\n  JwtNonStringException,\n  Jwt"
  },
  {
    "path": "json/json4s-common/src/test/scala/Json4sCommonFixture.scala",
    "chars": 1196,
    "preview": "package pdi.jwt\n\nimport org.json4s.*\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerCl"
  },
  {
    "path": "json/json4s-jackson/src/main/scala/JwtJson4sImplicits.scala",
    "chars": 298,
    "preview": "package pdi.jwt\n\nimport org.json4s.JValue\n\ntrait JwtJson4sImplicits {\n  implicit class RichJwtClaim(claim: JwtClaim) {\n "
  },
  {
    "path": "json/json4s-jackson/src/main/scala/JwtJson4sJackson.scala",
    "chars": 1074,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.jackson.JsonMethods.*\nimport org.json4s.j"
  },
  {
    "path": "json/json4s-jackson/src/test/scala/Json4sJacksonFixture.scala",
    "chars": 193,
    "preview": "package pdi.jwt\n\nimport org.json4s.*\nimport org.json4s.jackson.JsonMethods.*\n\ntrait Json4sJacksonFixture extends Json4sC"
  },
  {
    "path": "json/json4s-jackson/src/test/scala/Json4sJacksonSpec.scala",
    "chars": 1388,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.JsonDSL.*\n\nclass JwtJson4sJacksonSpec ext"
  },
  {
    "path": "json/json4s-native/src/main/scala/JwtJson4sImplicits.scala",
    "chars": 298,
    "preview": "package pdi.jwt\n\nimport org.json4s.JValue\n\ntrait JwtJson4sImplicits {\n  implicit class RichJwtClaim(claim: JwtClaim) {\n "
  },
  {
    "path": "json/json4s-native/src/main/scala/JwtJson4sNative.scala",
    "chars": 1071,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.native.JsonMethods.*\nimport org.json4s.na"
  },
  {
    "path": "json/json4s-native/src/test/scala/Json4sNativeFixture.scala",
    "chars": 191,
    "preview": "package pdi.jwt\n\nimport org.json4s.*\nimport org.json4s.native.JsonMethods.*\n\ntrait Json4sNativeFixture extends Json4sCom"
  },
  {
    "path": "json/json4s-native/src/test/scala/Json4sNativeSpec.scala",
    "chars": 1370,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport org.json4s.*\nimport org.json4s.JsonDSL.*\n\nclass JwtJson4sNativeSpec exte"
  },
  {
    "path": "json/play-json/src/main/scala/JwtJson.scala",
    "chars": 2147,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport pdi.jwt.exceptions.{JwtNonNumberException, JwtNonStringException, JwtVal"
  },
  {
    "path": "json/play-json/src/main/scala/JwtJsonImplicits.scala",
    "chars": 3971,
    "preview": "package pdi.jwt\n\nimport pdi.jwt.exceptions.{\n  JwtNonNumberException,\n  JwtNonStringException,\n  JwtNonStringSetOrString"
  },
  {
    "path": "json/play-json/src/test/scala/JsonFixture.scala",
    "chars": 971,
    "preview": "package pdi.jwt\n\nimport play.api.libs.json.JsObject\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: Strin"
  },
  {
    "path": "json/play-json/src/test/scala/JwtJsonSpec.scala",
    "chars": 3615,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.util.Failure\nimport scala.util.Success\n\nimport pdi.jwt.exceptions.J"
  },
  {
    "path": "json/upickle/jvm/src/test/scala/JwtUpickleFixture.scala",
    "chars": 838,
    "preview": "package pdi.jwt\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    header: String,\n    headerClass: JwtHeader,\n    h"
  },
  {
    "path": "json/upickle/jvm/src/test/scala/JwtUpickleSpec.scala",
    "chars": 194,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nclass JwtUpickleSpec extends JwtJsonCommonSpec[ujson.Value] with JwtUpickleFixt"
  },
  {
    "path": "json/upickle/shared/src/main/scala/JwtUpickle.scala",
    "chars": 1133,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport upickle.default.*\n\n/** Implementation of `JwtCore` using `Js.Value` from"
  },
  {
    "path": "json/upickle/shared/src/main/scala/JwtUpickleImplicits.scala",
    "chars": 2082,
    "preview": "package pdi.jwt\n\nimport pdi.jwt.exceptions.JwtNonStringSetOrStringException\nimport upickle.default.*\n\ntrait JwtUpickleIm"
  },
  {
    "path": "json/zio-json/src/main/scala/JwtZIOJson.scala",
    "chars": 2440,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport zio.json.*\nimport zio.json.ast.*\nimport zio.json.ast.JsonCursor.*\n\ntrait"
  },
  {
    "path": "json/zio-json/src/test/scala/JwtZIOJsonSpec.scala",
    "chars": 219,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\n\nimport zio.json.ast.Json\n\nclass JwtZIOJsonSpec extends JwtJsonCommonSpec[Json] "
  },
  {
    "path": "json/zio-json/src/test/scala/ZIOJsonFixture.scala",
    "chars": 908,
    "preview": "package pdi.jwt\n\nimport zio.json._\nimport zio.json.ast.Json\n\ncase class JsonDataEntry(\n    algo: JwtAlgorithm,\n    heade"
  },
  {
    "path": "play/src/main/scala/JwtPlayImplicits.scala",
    "chars": 6022,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport javax.inject.Inject\n\nimport play.api.Configuration\nimport play.api.libs.j"
  },
  {
    "path": "play/src/main/scala/JwtSession.scala",
    "chars": 8401,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport javax.inject.Inject\nimport scala.concurrent.duration.Duration\n\nimport pdi"
  },
  {
    "path": "play/src/test/scala/JwtResultSpec.scala",
    "chars": 3103,
    "preview": "package pdi.jwt\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configuration\nimport play.api.inject.guice."
  },
  {
    "path": "play/src/test/scala/JwtSessionAsymetricSpec.scala",
    "chars": 6589,
    "preview": "package pdi.jwt\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configur"
  },
  {
    "path": "play/src/test/scala/JwtSessionCustomDifferentNameSpec.scala",
    "chars": 6089,
    "preview": "package pdi.jwt\n\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configu"
  },
  {
    "path": "play/src/test/scala/JwtSessionCustomSpec.scala",
    "chars": 6435,
    "preview": "package pdi.jwt\n\nimport java.time.{Clock, Duration}\n\nimport org.apache.pekko.stream.Materializer\nimport play.api.Configu"
  },
  {
    "path": "play/src/test/scala/JwtSessionSpec.scala",
    "chars": 6391,
    "preview": "package pdi.jwt\n\nimport scala.concurrent.duration.Duration\n\nimport org.apache.pekko.stream.Materializer\nimport play.api."
  },
  {
    "path": "play/src/test/scala/PlayFixture.scala",
    "chars": 2626,
    "preview": "package pdi.jwt\n\nimport java.time.Clock\nimport scala.concurrent.Future\n\nimport org.apache.pekko.stream.Materializer\nimpo"
  },
  {
    "path": "project/Libs.scala",
    "chars": 2173,
    "preview": "import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._\nimport sbt._\n\nobject Versions {\n  val munit = \""
  },
  {
    "path": "project/build.properties",
    "chars": 19,
    "preview": "sbt.version=1.12.9\n"
  },
  {
    "path": "project/plugins.sbt",
    "chars": 1028,
    "preview": "// Documentation\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-site-paradox\" % \"1.7.0\")\n\naddSbtPlugin(\"com.github.sbt\" % \"sbt-un"
  },
  {
    "path": "scripts/bump.sh",
    "chars": 352,
    "preview": "#!/bin/bash\n\nOLDVERSION=$1\nVERSION=$2\n\necho \"Stating publish process for JWT Scala from $OLDVERSION to $VERSION ...\"\n\nec"
  },
  {
    "path": "scripts/clean.sh",
    "chars": 40,
    "preview": "find . -name \"*.tmp\" -exec rm -rf {} \\;\n"
  },
  {
    "path": "scripts/pu.sh",
    "chars": 263,
    "preview": "#!/bin/bash\n\nVERSION=$1\n\necho \"Pushing to GitHub\"\ngit add .\ngit commit -m \"Release v$VERSION\"\ngit tag -a v$VERSION -m \"R"
  }
]

About this extraction

This page contains the full source code of the pauldijou/jwt-scala GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (293.7 KB), approximately 85.9k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!