README.md

# nova_auth_oidc

OpenID Connect authentication for [Nova](https://github.com/novaframework/nova) web applications.

Provides OIDC login flows, JWT bearer validation, token introspection, and
client credentials (M2M) -- all integrated with [nova_auth](https://github.com/Taure/nova_auth)'s
unified actor session.

## Features

- **Multi-provider OIDC** -- Authentik, Google, GitHub, Keycloak, etc.
- **JWT bearer validation** -- protect API routes with provider-issued JWTs
- **Token introspection** -- check revocation status (RFC 7662)
- **Client credentials** -- machine-to-machine tokens with caching
- **Claims mapping** -- transform provider claims to actor maps via [nova_auth_claims](https://github.com/Taure/nova_auth)
- **Nova integration** -- security callbacks, plugins, route protection

## Quick Start

```erlang
%% 1. Define your OIDC config
-module(my_oidc_config).
-behaviour(nova_auth_oidc).
-export([config/0]).

config() ->
    #{providers => #{
          authentik => #{
              issuer => ~"https://auth.example.com/application/o/myapp",
              client_id => os:getenv("AUTHENTIK_CLIENT_ID"),
              client_secret => os:getenv("AUTHENTIK_CLIENT_SECRET")
          }
      },
      base_url => ~"https://myapp.example.com",
      claims_mapping => #{
          ~"sub" => id, ~"email" => email, ~"groups" => roles
      }}.

%% 2. Start providers in your app's start/2
start(_Type, _Args) ->
    nova_auth_oidc:ensure_providers(my_oidc_config),
    my_sup:start_link().

%% 3. Add routes
routes(_Env) ->
    [#{prefix => ~"/auth", security => false,
       routes => [
           {~"/:provider/login", fun nova_auth_oidc_controller:login/1,
            #{auth_mod => my_oidc_config}},
           {~"/:provider/callback", fun nova_auth_oidc_controller:callback/1,
            #{auth_mod => my_oidc_config}}
       ]},
     #{prefix => ~"/dashboard",
       security => nova_auth_security:require_authenticated(),
       routes => [...]},
     #{prefix => ~"/api",
       security => nova_auth_oidc_security:require_bearer(my_oidc_config),
       routes => [...]}].
```

## Modules

| Module | Description |
|--------|-------------|
| `nova_auth_oidc` | Behaviour-based config, provider worker management |
| `nova_auth_oidc_controller` | Login redirect and OAuth callback endpoints |
| `nova_auth_oidc_plugin` | Route protection plugin (session-based) |
| `nova_auth_oidc_jwt` | JWT bearer token validation via provider JWKS |
| `nova_auth_oidc_security` | Security callbacks: `require_bearer/1`, `require_any/1` |
| `nova_auth_oidc_introspect` | Token introspection (RFC 7662) |
| `nova_auth_oidc_client_credentials` | Client credentials flow with caching |

## How It Works

1. User visits `/auth/authentik/login`
2. Controller generates nonce + PKCE, stores in session, redirects to provider
3. User authenticates at Authentik
4. Authentik redirects to `/auth/authentik/callback?code=...`
5. Controller exchanges code for tokens via `oidcc`
6. Controller retrieves userinfo from provider
7. Claims are mapped to an actor via `nova_auth_claims`
8. Actor is stored in session via `nova_auth_actor`
9. User is redirected to the success URL

From this point, `nova_auth_security:require_authenticated()` works for all
protected routes.

## Guides

- [Getting Started](guides/getting-started.md) -- Installation and first setup
- [Configuration](guides/configuration.md) -- Full config reference
- [JWT Bearer](guides/jwt-bearer.md) -- Protecting API routes with JWTs
- [Claims Mapping](guides/claims-mapping.md) -- Transforming provider claims
- [Client Credentials](guides/client-credentials.md) -- Machine-to-machine auth

## Dependencies

- [nova_auth](https://github.com/Taure/nova_auth) -- unified actor session, claims mapping, policies
- [oidcc](https://github.com/erlef/oidcc) -- ERLEF OpenID Connect Certified client
- [nova](https://github.com/novaframework/nova) -- web framework

## Requirements

- Erlang/OTP 28+

## License

MIT