# 107. JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. JWT is commonly used for authentication and authorization in web applications. It allows the server to verify the identity of a user and send claims, such as user roles or permissions, in a secure way.

Here's how you can work with JWT in Python, using the `PyJWT` library to encode and decode JWTs for secure communication:

**1. Install PyJWT**

First, you need to install the `PyJWT` library. You can install it using pip:

```bash
pip install pyjwt
```

***

**2. Encoding a JWT**

This example shows how to encode a payload into a JWT token.

```python
import jwt
import datetime

# Secret key used to encode and decode the JWT
SECRET_KEY = "your_secret_key"

# Payload (data to encode into the JWT)
payload = {
    "user_id": 123,
    "username": "alice",
    "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)  # Expiration time
}

# Encode the JWT with the payload and the secret key
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"JWT Token: {token}")
```

**Explanation**:

* The `payload` contains the claims you want to store, such as `user_id`, `username`, and an expiration time (`exp`).
* The `jwt.encode()` function encodes the data and signs it with the secret key using the `HS256` algorithm.

***

**3. Decoding a JWT**

You can decode a JWT token to retrieve the payload data.

```python
import jwt

# JWT token received (replace with the actual token you received)
token = "your_jwt_token_here"

try:
    # Decode the token using the secret key
    decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    print(f"Decoded Payload: {decoded_payload}")
except jwt.ExpiredSignatureError:
    print("The token has expired.")
except jwt.InvalidTokenError:
    print("Invalid token.")
```

**Explanation**:

* The `jwt.decode()` function decodes the token and validates its signature using the secret key.
* If the token is expired or invalid, an exception is raised, which can be handled appropriately.

***

**4. JWT with Custom Claims**

You can also include custom claims in the JWT.

```python
import jwt
import datetime

SECRET_KEY = "your_secret_key"

# Payload with custom claims
payload = {
    "sub": "1234567890",  # Subject (user ID, for example)
    "name": "John Doe",
    "iat": datetime.datetime.utcnow(),  # Issued at time
    "role": "admin",  # Custom claim for user role
    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)  # Expiration time
}

# Encode the JWT with custom claims
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"JWT Token with custom claims: {token}")
```

***

**5. Validating JWT Expiration**

JWTs can include an expiration (`exp`) claim to limit their validity. When decoding the token, the `exp` claim is automatically checked to ensure the token has not expired.

```python
import jwt

token = "your_jwt_token_here"
SECRET_KEY = "your_secret_key"

try:
    # Decode and validate token
    decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    print(f"Valid token with payload: {decoded_payload}")
except jwt.ExpiredSignatureError:
    print("Token has expired.")
except jwt.InvalidTokenError:
    print("Invalid token.")
```

***

**6. JWT in a Web Application (Flask Example)**

A typical usage of JWT in a web application is for user authentication. Here is an example of how JWT can be used in a Flask web app:

```python
from flask import Flask, request, jsonify
import jwt
import datetime

app = Flask(__name__)
SECRET_KEY = "your_secret_key"

@app.route('/login', methods=['POST'])
def login():
    # Simulate user authentication
    user = {"username": "alice", "user_id": 123}
    
    # Create payload with user data and expiration time
    payload = {
        "user_id": user["user_id"],
        "username": user["username"],
        "exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }
    
    # Encode JWT
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return jsonify({"token": token})

@app.route('/protected', methods=['GET'])
def protected():
    token = request.headers.get('Authorization')
    
    if not token:
        return jsonify({"message": "Token is missing"}), 403
    
    try:
        # Decode JWT
        decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return jsonify({"message": "Protected data", "user": decoded_payload})
    except jwt.ExpiredSignatureError:
        return jsonify({"message": "Token has expired"}), 401
    except jwt.InvalidTokenError:
        return jsonify({"message": "Invalid token"}), 403

if __name__ == "__main__":
    app.run(debug=True)
```

**Explanation**:

* The `/login` route simulates user authentication and returns a JWT token.
* The `/protected` route requires a valid JWT token in the `Authorization` header to access protected resources. If the token is expired or invalid, the request will be denied.

***

**7. JWT with Refresh Tokens**

You can implement a refresh token mechanism to extend the session without requiring the user to log in again.

```python
import jwt
import datetime

SECRET_KEY = "your_secret_key"
REFRESH_SECRET_KEY = "your_refresh_secret_key"

# Simulate an access token and refresh token scenario
access_payload = {
    "user_id": 123,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
}
refresh_payload = {
    "user_id": 123,
    "exp": datetime.datetime.utcnow() + datetime.timedelta(days=30)
}

# Encode access token and refresh token
access_token = jwt.encode(access_payload, SECRET_KEY, algorithm="HS256")
refresh_token = jwt.encode(refresh_payload, REFRESH_SECRET_KEY, algorithm="HS256")

print(f"Access Token: {access_token}")
print(f"Refresh Token: {refresh_token}")
```

**Explanation**:

* The access token has a short expiration time (15 minutes), while the refresh token has a much longer expiration (30 days).
* When the access token expires, the client can send the refresh token to request a new access token.

***

**8. Decoding JWT without Verification**

In some cases, you might want to decode a JWT without verifying its signature (e.g., for debugging or checking its contents without trusting the signature).

```python
import jwt

# JWT Token to decode
token = "your_jwt_token_here"

# Decode the token without verifying the signature
decoded_payload = jwt.decode(token, options={"verify_signature": False})
print(f"Decoded without verification: {decoded_payload}")
```

**Explanation**: The `verify_signature=False` option disables signature verification. This is only recommended for trusted environments.

***

**9. JWT with Multiple Claims**

JWT can hold multiple claims. Here’s an example of using various claims like `sub`, `iat`, and `aud`:

```python
import jwt
import datetime

payload = {
    "sub": "1234567890",  # Subject
    "iat": datetime.datetime.utcnow(),  # Issued At time
    "aud": "example.com",  # Audience
    "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30)  # Expiration time
}

token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"JWT with multiple claims: {token}")
```

**Explanation**: The JWT includes various claims such as `sub` (subject), `iat` (issued at), `aud` (audience), and `exp` (expiration time).

***

**10. JWT Payload Expiration Check**

JWT automatically checks for expiration when decoding the token. Here's how you can handle token expiration explicitly.

```python
import jwt

token = "your_jwt_token_here"
SECRET_KEY = "your_secret_key"

try:
    # Decode the token and check expiration automatically
    decoded_payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
    print(f"Token payload: {decoded_payload}")
except jwt.ExpiredSignatureError:
    print("The token has expired.")
except jwt.InvalidTokenError:
    print("Invalid token.")
```

**Explanation**: If the `exp` claim in the JWT has passed, a `ExpiredSignatureError` is raised, allowing you to handle expired tokens appropriately.

***

**Key Concepts:**

* **JWT Encoding and Decoding**: Use `jwt.encode()` to generate JWTs and `jwt.decode()` to extract data.
* **Expiration and Claims**: Use the `exp` claim for expiration, and other claims like `sub`, `iat`, and `aud` for subject, issued-at, and audience information.
* **Security**: Always protect your JWTs with a strong secret key to prevent tampering and ensure they are valid.

JWTs are widely used in web applications for securely transmitting information between clients and servers, especially in authentication and authorization contexts.
