scg-auth

Authorization Code + PKCE

The most secure flow for any app that has a browser. PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks.

Your App          Your Browser        OAuth Server
    |                   |                   |
    |── generateAuthUrl ─►                  |
    |   state (CSRF)    |                   |
    |   code_challenge  |                   |
    |                   │── GET /authorize ─►|
    |                   |                   | Show login form
    |                   |◄── 302 /callback ─|
    |◄── exchangeCode ──|                   |
    |   validates state |                   |
    |   sends verifier  │── POST /token ───►|
    |                   |                   | Verify PKCE ✓
    |◄── access_token ──────────────────────|

Step-by-step

=== “Node.js”

```js
const SCGAuth = require('scg-auth');

const client = new SCGAuth({
  clientId:         'my-app',
  clientSecret:     'optional-for-public-clients',
  authorizationUrl: 'https://your-server.com/authorize',
  tokenUrl:         'https://your-server.com/token',
  redirectUri:      'http://localhost:3000/callback',
  scopes:           ['openid', 'email', 'profile'],
});

// ── Step 1: Build the auth URL ──────────────────────────────────────────
const { url, state, codeVerifier } = client.generateAuthUrl({
  pkce: true,            // enable PKCE S256 (recommended)
  additionalParams: {    // any extra params your server needs
    prompt: 'login',
    acr_values: 'urn:mace:incommon:iap:silver',
  },
});

// Redirect the user:
// res.redirect(url);

// ── Step 2: Handle callback at /callback ────────────────────────────────
// req.query = { code: '...', state: '...' }
const tokens = await client.exchangeCode(req.query.code, {
  state: req.query.state,  // scg-auth verifies this matches what was sent
  // codeVerifier is stored internally — no need to pass it
});

// tokens =
// {
//   access_token:  'eyJ...',
//   token_type:    'Bearer',
//   expires_in:    3600,
//   refresh_token: 'def...',
//   scope:         'openid email profile',
// }

// ── Step 3: Check token status ──────────────────────────────────────────
console.log(client.isTokenExpired());         // false  — not expired
console.log(client.isTokenExpired(3600));      // true   — expires within 1 hour
console.log(client.getStoredTokens());         // full token object
```

=== “Python”

```python
from scg_auth import SCGAuth, OAuthError

client = SCGAuth(
    client_id='my-app',
    client_secret='optional',
    authorization_url='https://your-server.com/authorize',
    token_url='https://your-server.com/token',
    redirect_uri='http://localhost:3000/callback',
    scopes=['openid', 'email', 'profile'],
)

# Step 1: Build auth URL
result = client.generate_auth_url(pkce=True)
# result = { 'url': '...', 'state': '...', 'code_verifier': '...' }

# Step 2: Exchange code after redirect
try:
    tokens = client.exchange_code(code, state=result['state'])
except OAuthError as e:
    print(e.oauth_error)   # e.g. 'invalid_grant'
    print(e.status_code)

# Step 3: Check status
print(client.is_token_expired())        # False
print(client.is_token_expired(3600))    # True (expires within 1hr)
print(client.get_stored_tokens())       # full token dict
```

Security notes

!!! tip “PKCE replaces client_secret for public clients” For browser apps and mobile apps, PKCE provides the same protection as a client_secret without embedding a secret in client-side code.

!!! warning “Always validate state” Pass state to exchangeCode / exchange_code. scg-auth will throw if it doesn’t match, preventing CSRF attacks.