<div align="center">
  <h1>better-elysia-oauth2</h1>
  <p>An Elysia plugin for OAuth 2.0 Authorization Flow with <strong>60+ providers</strong> powered by <a href="https://arcticjs.dev/">Arctic</a>.</p>

  <p>
    <a href="#installation">Installation</a> •
    <a href="#quick-start">Quick Start</a> •
    <a href="./DOCS.md">Documentation</a> •
    <a href="#features">Features</a>
  </p>
</div>

## Features

- **60+ OAuth providers** — Google, GitHub, Discord, Twitter, Spotify and more via Arctic
- **CSRF protection** — automatic state verification
- **PKCE support** — automatic code verifier for providers that require it
- **OpenID Connect** — automatic ID token decoding
- **Payload passthrough** — store arbitrary data in the state and retrieve it on callback
- **Type-safe** — full TypeScript inference for provider instances and options
- **Minimal** — single plugin, no boilerplate

## Installation

```bash
bun add better-elysia-oauth2 arctic
```

### Update

if [Arctic](https://arcticjs.dev/) will release some new providers, you can update it with

```bash
bun add arctic@latest
```

## Quick Start

```typescript
import { Elysia } from 'elysia';
import { oauth2 } from 'better-elysia-oauth2';

const app = new Elysia()
  .use(
    oauth2({
      GitHub: ['client_id', 'client_secret', 'http://localhost:3000/callback'],
    }),
  )
  .get('/auth', ({ oauth2 }) => oauth2.redirect('GitHub', ['user:email', 'repo']))
  .get('/auth/callback', async ({ oauth2 }) => {
    const { tokens, payload } = await oauth2.authorize();
    const accessToken = tokens.accessToken();

    // Send request to API with token...
  })
  .listen(3000);
```

### Using createURL

Use `createURL` when you need to modify the authorization URL before redirecting (e.g., adding provider-specific `searchParams` like `access_type=offline` for Google):

```typescript
app.get('/auth/google', ({ oauth2, redirect }) => {
  const url = oauth2.createURL('Google', ['openid', 'email', 'profile']);
  url.searchParams.set('access_type', 'offline');
  url.searchParams.set('prompt', 'consent');
  
  return redirect(url.href);
});
```

### Payload

You can store arbitrary data in the state and retrieve it on callback

```typescript
app
  .get('/auth', ({ oauth2, query }) => oauth2.redirect(
      'GitHub',
      ['user:email', 'repo'],
      { redirectBackTo: query.r } // Payload
    ),
    {
      query: t.Object({
        r: t.Optional(t.String())
      })
    }
  )
  .get('/auth/callback', async ({ oauth2, cookie, redirect }) => {
    const { tokens, payload } = await oauth2.authorize();
    const accessToken = tokens.accessToken();

    // Send request to API with token...

    return redirect(payload.redirectBackTo)
  })
```

## Documentation

See the [full documentation](./DOCS.md) for detailed guides on providers, advanced usage, error handling, and API reference.

## License

MIT
