Metadata-Version: 2.4
Name: fastjwt-kit
Version: 0.2.0
Summary: A small, opinionated JWT toolkit: create, verify, expire, and handle errors without writing the same PyJWT boilerplate every project.
Project-URL: Homepage, https://github.com/Shreyas-Gowda26/fastjwt-kit
Project-URL: Issues, https://github.com/Shreyas-Gowda26/fastjwt-kit/issues
Author-email: Shreyas G <shreyasg2526@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: auth,authentication,fastapi,jwt,security,token
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: bcrypt>=4.0.0
Requires-Dist: pyjwt>=2.8.0
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: cryptography>=3.4; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# fastjwt-kit

A small, opinionated wrapper around [PyJWT](https://pyjwt.readthedocs.io/) that
handles the boilerplate every project rewrites: token creation, expiry,
claim validation, and clean error handling, without importing `jwt` exceptions
directly.

## Install

```bash
pip install fastjwt-kit
```

For asymmetric algorithms (RS256, ES256), install the `cryptography` package as well:

```bash
pip install fastjwt-kit cryptography
```

## Quickstart

```python
from fastjwt_kit import create_access_token, verify_token, TokenExpiredError

token = create_access_token(subject="user-123", secret="change-me")

try:
    claims = verify_token(token, secret="change-me")
    print(claims["sub"])  # "user-123"
except TokenExpiredError:
    print("Token expired, ask the user to log in again")
```

No manual `exp` math, no catching raw `jwt.ExpiredSignatureError`.

## Token types

`create_access_token` and `create_refresh_token` are convenience wrappers that
set sensible defaults and inject a `type` claim:

```python
from fastjwt_kit import create_access_token, create_refresh_token

access  = create_access_token(subject="user-123", secret="change-me")   # 15 min, type: "access"
refresh = create_refresh_token(subject="user-123", secret="change-me")  # 7 days, type: "refresh"
```

Use the `type` claim in the decoded payload to distinguish tokens:

```python
claims = verify_token(access, secret="change-me")
assert claims["type"] == "access"
```

## Creating tokens

### `create_token`

Full control over all claims:

```python
import datetime as dt
from fastjwt_kit import create_token

token = create_token(
    subject="user-123",
    secret="change-me",
    expires_in=dt.timedelta(hours=1),
    issuer="my-app",
    audience="my-app-clients",
    extra_claims={"role": "admin"},
)
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `subject` | `str` | Yes | Value of the `sub` claim, typically a user ID. |
| `secret` | `str` | Yes | Signing secret for HS256, or a private key object/PEM bytes for asymmetric algorithms. |
| `expires_in` | `timedelta` | Yes | How long until the token expires. |
| `algorithm` | `str` | No | Signing algorithm. Defaults to `"HS256"`. |
| `issuer` | `str` | No | Value of the `iss` claim. |
| `audience` | `str` | No | Value of the `aud` claim. |
| `extra_claims` | `dict` | No | Additional claims to embed in the payload (for example `{"role": "admin"}`). |

### `create_access_token` / `create_refresh_token`

Same parameters as `create_token`, but `expires_in` has a built-in default:

| Function | Default `expires_in` | Auto-added claim |
|---|---|---|
| `create_access_token` | 15 minutes | `type: "access"` |
| `create_refresh_token` | 7 days | `type: "refresh"` |

## Verifying tokens

```python
from fastjwt_kit import verify_token

claims = verify_token(
    token,
    secret="change-me",
    issuer="my-app",
    audience="my-app-clients",
)
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `token` | `str` | Yes | The encoded JWT string. |
| `secret` | `str` | Yes | Signing secret for HS256, or the public key object/PEM bytes for asymmetric algorithms. |
| `algorithm` | `str` | No | Expected signing algorithm. Defaults to `"HS256"`. Must match the algorithm used to sign. |
| `issuer` | `str` | No | If provided, the token's `iss` claim must match exactly. |
| `audience` | `str` | No | If provided, the token's `aud` claim must match exactly. |

Returns a `dict[str, Any]` of the decoded claims on success.

## Asymmetric algorithms (RS256, ES256)

For cross-service token exchange or anywhere you cannot share a symmetric
secret, use an asymmetric algorithm. Pass key objects directly:

```python
import datetime as dt
from cryptography.hazmat.primitives.asymmetric import rsa
from fastjwt_kit import create_token, verify_token

# Generate a key pair once and store securely
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key  = private_key.public_key()

# Sign with the private key
token = create_token(
    subject="user-123",
    secret=private_key,
    expires_in=dt.timedelta(hours=1),
    algorithm="RS256",
)

# Verify with the public key
claims = verify_token(token, secret=public_key, algorithm="RS256")
```

The `algorithm` value must match in both `create_token` and `verify_token`.
Passing a mismatched algorithm to `verify_token` raises `InvalidTokenError`.

## Error handling

All errors inherit from `TokenError`:

```
TokenError
├── TokenExpiredError      token's exp claim has passed
├── InvalidTokenError      malformed, tampered, or wrong algorithm
└── InvalidClaimsError     iss, aud, or other required claims do not match
```

Catch broadly or specifically:

```python
from fastjwt_kit import TokenError, TokenExpiredError, InvalidTokenError, InvalidClaimsError

try:
    claims = verify_token(token, secret="change-me")
except TokenExpiredError:
    ...  # token was valid but has expired
except InvalidClaimsError:
    ...  # issuer or audience did not match
except InvalidTokenError:
    ...  # malformed or tampered token
except TokenError:
    ...  # catch-all for any token error
```

You never need to import `jwt` exceptions directly.

## API reference

### Functions

| Function | Returns | Description |
|---|---|---|
| `create_token(subject, secret, *, expires_in, ...)` | `str` | Create a JWT with full control over all claims. |
| `create_access_token(subject, secret, ...)` | `str` | Short-lived token (default 15 min), tagged `type: "access"`. |
| `create_refresh_token(subject, secret, ...)` | `str` | Long-lived token (default 7 days), tagged `type: "refresh"`. |
| `verify_token(token, secret, *, algorithm, issuer, audience)` | `dict[str, Any]` | Decode and validate a token, raising typed exceptions on failure. |

### Exceptions

| Exception | Raised when |
|---|---|
| `TokenError` | Base class. Catch this to handle any token error. |
| `TokenExpiredError` | The token's `exp` claim has passed. |
| `InvalidTokenError` | The token is malformed, the signature is invalid, or the algorithm does not match. |
| `InvalidClaimsError` | Required claims (`iss`, `aud`) are missing or do not match expected values. |

## Why not just use PyJWT directly?

You can. This package wraps the parts everyone ends up rewriting:

- Expiry defaults so you do not hardcode `timedelta(minutes=15)` in every project
- `iss` and `aud` validation plumbed through without managing `options` dicts manually
- Exception types that do not leak the underlying library, so upgrading PyJWT does not break your error handling

If you only need raw encode/decode, PyJWT alone is fine.

## Roadmap

- [ ] Password hashing module
- [ ] FastAPI signup/login router and `get_current_user` dependency
- [ ] Config/env validation helpers

Feedback and issues welcome. This is an early release and will evolve based on real usage.

## License

MIT
