Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@

* [Moves & Movesets](battling/moves-and-movesets.md)
* [Battles](battling/battles.md)

## API

* [Authentication](api/authentication.md)
* [Rate Limiting](api/rate-limiting.md)
301 changes: 301 additions & 0 deletions api/authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
---
description: >-
Learn how to authenticate with the Pokétwo API using API keys and OAuth,
and follow security best practices to keep your credentials safe.
---

# Authentication

{% hint style="warning" %}
This site is an early **work in progress**. Many pages may be missing or incomplete. Please let us know at [discord.gg/poketwo](https://discord.gg/poketwo) if you would like to help write or improve a page.
{% endhint %}

The Pokétwo API supports two authentication methods: **API key authentication** for server-to-server integrations and **OAuth 2.0** for applications acting on behalf of a user.

## API Key Authentication

API keys provide a simple way to authenticate requests. Each key is **scoped per-bot** — meaning every bot registered under your account has its own independent set of API keys. Keys created for one bot cannot be used to access resources belonging to another bot.

### Obtaining an API Key

1. Log in to the [Pokétwo Developer Portal](https://poketwo.net/developers).
2. Navigate to **Settings > API Keys**.
3. Click **Create New Key**.
4. Give your key a descriptive name and select the permission scopes it needs.
5. Copy the key immediately — it will only be displayed once.

{% hint style="danger" %}
Treat your API key like a password. Never share it publicly or commit it to version control. If you believe a key has been compromised, revoke it immediately and create a new one.
{% endhint %}

### Using Your API Key

Include the API key in the `Authorization` header of every request using the `Bearer` scheme.

```bash
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.poketwo.net/v1/pokemon
```

#### Python

```python
import requests

headers = {"Authorization": "Bearer YOUR_API_KEY"}
response = requests.get("https://api.poketwo.net/v1/pokemon", headers=headers)
print(response.json())
```

#### JavaScript

```javascript
const response = await fetch("https://api.poketwo.net/v1/pokemon", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
const data = await response.json();
console.log(data);
```

### API Key Scopes

When creating an API key, you can restrict its access to specific scopes.

| Scope | Description |
| ------------------ | -------------------------------------------- |
| `pokemon:read` | View Pokémon data (species, stats, etc.). |
| `pokemon:write` | Modify Pokémon data (nicknames, etc.). |
| `market:read` | View market listings. |
| `market:write` | Create and manage market listings. |
| `user:read` | View user profile information. |
| `user:write` | Update user settings. |
| `trade:read` | View trade history. |
| `trade:write` | Initiate and manage trades. |

{% hint style="info" %}
Always follow the principle of least privilege — only grant your key the scopes it actually needs.
{% endhint %}

## OAuth 2.0

OAuth 2.0 allows third-party applications to access the API on behalf of a user without exposing their credentials. Pokétwo implements the **Authorization Code** flow.

### Registering Your Application

1. Go to the [Pokétwo Developer Portal](https://poketwo.net/developers).
2. Navigate to **Applications** and click **New Application**.
3. Fill in your application name, description, and **Redirect URI**.
4. Note your **Client ID** and **Client Secret**.

### Authorization Code Flow

The OAuth 2.0 Authorization Code flow consists of four steps:

#### Step 1: Redirect the User to Authorize

Direct the user's browser to the authorization endpoint.

```
GET https://poketwo.net/oauth/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=pokemon:read user:read
&state=RANDOM_STATE_STRING
```

| Parameter | Description |
| --------------- | ------------------------------------------------------------------- |
| `client_id` | Your application's Client ID. |
| `redirect_uri` | The URI to redirect to after authorization (must match registered). |
| `response_type` | Must be `code`. |
| `scope` | Space-separated list of requested scopes. |
| `state` | A random string to prevent CSRF attacks (recommended). |

#### Step 2: User Grants Permission

The user reviews the requested permissions and clicks **Authorize**. Pokétwo redirects back to your `redirect_uri` with an authorization code.

```
https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE_STRING
```

#### Step 3: Exchange the Code for Tokens

Make a server-side POST request to exchange the authorization code for an access token.

```bash
curl -X POST https://poketwo.net/oauth/token \
-d "grant_type=authorization_code" \
-d "code=AUTHORIZATION_CODE" \
-d "redirect_uri=https://yourapp.com/callback" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
```

The response contains an access token and a refresh token:

```json
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBh...",
"scope": "pokemon:read user:read"
}
```

#### Step 4: Use the Access Token

Include the access token in the `Authorization` header, just like an API key.

```bash
curl -H "Authorization: Bearer ACCESS_TOKEN" \
https://api.poketwo.net/v1/user/me
```

### Refreshing Tokens

Access tokens expire after the duration indicated by `expires_in`. Use the refresh token to obtain a new access token without requiring the user to re-authorize.

```bash
curl -X POST https://poketwo.net/oauth/token \
-d "grant_type=refresh_token" \
-d "refresh_token=YOUR_REFRESH_TOKEN" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
```

```python
import requests

response = requests.post(
"https://poketwo.net/oauth/token",
data={
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
},
)

tokens = response.json()
new_access_token = tokens["access_token"]
```

### Revoking Tokens

To revoke an access or refresh token (e.g. when a user logs out), send a POST request to the revocation endpoint.

```bash
curl -X POST https://poketwo.net/oauth/revoke \
-d "token=TOKEN_TO_REVOKE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"
```

A successful revocation returns an HTTP 200 with an empty body. Revoking a refresh token also invalidates any access tokens issued from it.

### OAuth Endpoints Reference

| Endpoint | Method | Description |
| ------------------------------------- | ------ | ------------------------------------------------ |
| `https://poketwo.net/oauth/authorize` | GET | Authorization page — redirect users here. |
| `https://poketwo.net/oauth/token` | POST | Exchange auth codes, refresh tokens. |
| `https://poketwo.net/oauth/revoke` | POST | Revoke an access or refresh token. |
| `https://api.poketwo.net/v1/user/me` | GET | Fetch the authenticated user's profile. |

## Security Best Practices

1. **Never expose secrets client-side.** API keys, client secrets, and refresh tokens must only be used in server-side code. Never embed them in frontend JavaScript, mobile apps, or public repositories.

2. **Use environment variables.** Store credentials in environment variables or a secrets manager rather than hard-coding them in your source code.

```bash
export POKETWO_API_KEY="your-api-key-here"
```

```python
import os
api_key = os.environ["POKETWO_API_KEY"]
```

3. **Rotate keys regularly.** Periodically revoke old API keys and generate new ones to minimize the impact of a potential leak.

4. **Use HTTPS exclusively.** All requests to the Pokétwo API must be made over HTTPS. Never send credentials over unencrypted HTTP.

5. **Validate the `state` parameter.** When using OAuth, always generate a unique, random `state` value for each authorization request and verify it in the callback to prevent cross-site request forgery (CSRF) attacks.

6. **Restrict redirect URIs.** Register only the specific redirect URIs your application uses. Avoid wildcard or overly broad patterns.

7. **Handle token expiry gracefully.** Implement automatic token refresh logic so your application can recover from expired tokens without user intervention.

8. **Revoke tokens on logout.** When a user logs out of your application, revoke their access and refresh tokens to prevent unauthorized reuse.

## Webhook Signature Verification

If you register webhooks to receive event notifications, Pokétwo signs every webhook payload so you can verify it was not tampered with in transit. Each webhook request includes an `X-Signature-256` header containing an HMAC-SHA256 signature of the request body, computed using your webhook secret.

### Verifying the Signature

#### Python

```python
import hashlib
import hmac
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = os.environ["POKETWO_WEBHOOK_SECRET"]

@app.route("/webhook", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Signature-256", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(),
request.data,
hashlib.sha256,
).hexdigest()

if not hmac.compare_digest(signature, expected):
abort(403, "Invalid signature")

payload = request.json
# Process the event...
return "", 200
```

#### JavaScript (Node.js)

```javascript
const crypto = require("crypto");
const express = require("express");
const app = express();

const WEBHOOK_SECRET = process.env.POKETWO_WEBHOOK_SECRET;

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-signature-256"] || "";
const expected =
"sha256=" +
crypto.createHmac("sha256", WEBHOOK_SECRET).update(req.body).digest("hex");

if (
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
) {
return res.status(403).send("Invalid signature");
}

const payload = JSON.parse(req.body);
// Process the event...
res.sendStatus(200);
});
```

{% hint style="danger" %}
Always use constant-time comparison functions (`hmac.compare_digest` in Python, `crypto.timingSafeEqual` in Node.js) when verifying signatures. Standard string equality checks are vulnerable to timing attacks.
{% endhint %}

{% hint style="info" %}
For questions about authentication or to report a security issue, reach out at [discord.gg/poketwo](https://discord.gg/poketwo).
{% endhint %}
Loading