README.md

<div style="margin-right: 15px; float: left;">
  <img
    align="left"
    src="assets/logo.svg"
    alt="OpenID Connect Logo"
    width="170px"
  />
</div>

# oidcc

OpenID Connect client library for Erlang.

[![EEF Security WG project](https://img.shields.io/badge/EEF-Security-black)](https://github.com/erlef/security-wg)
[![Main Branch](https://github.com/erlef/oidcc/actions/workflows/branch_main.yml/badge.svg?branch=main)](https://github.com/erlef/oidcc/actions/workflows/branch_main.yml)
[![Module Version](https://img.shields.io/hexpm/v/oidcc.svg)](https://hex.pm/packages/oidcc)
[![Total Download](https://img.shields.io/hexpm/dt/oidcc.svg)](https://hex.pm/packages/oidcc)
[![License](https://img.shields.io/hexpm/l/oidcc.svg)](https://github.com/erlef/oidcc/blob/main/LICENSE)
[![Last Updated](https://img.shields.io/github/last-commit/erlef/oidcc.svg)](https://github.com/erlef/oidcc/commits/master)
[![Coverage Status](https://coveralls.io/repos/github/erlef/oidcc/badge.svg?branch=main)](https://coveralls.io/github/erlef/oidcc?branch=main)

<br clear="left"/>

<picture style="margin-right: 15px; float: left;">
  <source
    media="(prefers-color-scheme: dark)"
    srcset="assets/certified-dark.svg"
    width="170px"
    align="left"
  />
  <source
    media="(prefers-color-scheme: light)"
    srcset="assets/certified-light.svg"
    width="170px"
    align="left"
  />
  <img
    src="assets/certified-light.svg"
    alt="OpenID Connect Certified Logo"
    width="170px"
    align="left"
  />
</picture>

OpenID Certified by [Jonatan Männchen](https://github.com/maennchen) at the
[Erlang Ecosystem Foundation](https://github.com/erlef) of multiple Relaying
Party conformance profiles of the OpenID Connect protocol:
For details, check the
[Conformance Documentation](https://github.com/erlef/oidcc/tree/openid-foundation-certification).

<br clear="left"/>

<picture style="margin-right: 15px; float: left;">
  <source
    media="(prefers-color-scheme: dark)"
    srcset="assets/erlef-logo-dark.svg"
    width="170px"
    align="left"
  />
  <source
    media="(prefers-color-scheme: light)"
    srcset="assets/erlef-logo-light.svg"
    width="170px"
    align="left"
  />
  <img
    src="assets/erlef-logo-light.svg"
    alt="Erlang Ecosystem Foundation Logo"
    width="170px"
    align="left"
  />
</picture>

The refactoring for `v3` and the certification is funded as an
[Erlang Ecosystem Foundation](https://erlef.org/) stipend entered by the
[Security Working Group](https://erlef.org/wg/security).

<br clear="left"/>

## Supported Features

* [Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)
  (`[ISSUER]/.well-known/openid-configuration`)
* [Client Registration](https://openid.net/specs/openid-connect-registration-1_0.html)
* Authorization (Code Flow)
  * [Request Object](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject)
  * [PKCE](https://oauth.net/2/pkce/)
* Token
  * Authorization: `client_secret_basic`, `client_secret_post`,
    `client_secret_jwt`, and `private_key_jwt`
  * Grant Types: `authorization_code`, `refresh_token`, `jwt_bearer`, and
    `client_credentials`
  * Automatic JWK Refreshing when needed
* Userinfo
  * [JWT Response](https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse)
  * [Aggregated and Distributed Claims](https://openid.net/specs/openid-connect-core-1_0.html#AggregatedDistributedClaims)
* [Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)
* Logout
  * [RP-Initiated](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)

## Setup

**Please note that the minimum supported Erlang OTP version is OTP26.**

### Erlang

**directly**

```erlang
{ok, Pid} =
  oidcc_provider_configuration_worker:start_link(#{
    issuer => <<"https://accounts.google.com">>,
    name => {local, google_config_provider}
  }).
```

**via `supervisor`**

```erlang
-behaviour(supervisor).

%% ...

init(_Args) ->
  SupFlags = #{strategy => one_for_one},
  ChildSpecs = [#{id => oidcc_provider_configuration_worker,
                  start => {oidcc_provider_configuration_worker, start_link, [
                    #{issuer => "https://accounts.google.com",
                      name => {local, myapp_oidcc_config_provider}}
                  ]},
                  shutdown => brutal_kill}],
  {ok, {SupFlags, ChildSpecs}}.
```

### Elixir

**directly**

```elixir
{:ok, _pid} =
  Oidcc.ProviderConfiguration.Worker.start_link(%{
  issuer: "https://accounts.google.com",
  name: Myapp.OidccConfigProvider
})
```

**via `Supervisor`**

```elixir
Supervisor.init([
  {Oidcc.ProviderConfiguration.Worker, %{
    issuer: "https://accounts.google.com",
    name: Myapp.OidccConfigProvider
  }}
], strategy: :one_for_one)
```

## Usage

### Companion libraries

`oidcc` offers integrations for various libraries:

<!-- TODO: Uncomment when available -->

- [`oidcc_cowboy`](https://hex.pm/packages/oidcc_cowboy) - Integrations for
  [`cowboy`](https://hex.pm/packages/cowboy)
- [`oidcc_plug`](https://hex.pm/packages/oidcc_plug) - Integrations for
  [`plug`](https://hex.pm/packages/plug) and
  [`phoenix`](https://hex.pm/packages/phoenix)
- [`phx_gen_oidcc`](https://hex.pm/packages/phx_gen_oidcc) - Setup Generator for
  [`phoenix`](https://hex.pm/packages/phoenix)
- [`ueberauth_oidcc`](https://hex.pm/packages/ueberauth_oidcc) - Integration for
  [`ueberauth`](https://hex.pm/packages/ueberauth)

### Erlang

```erlang
%% Create redirect URI for authorization
{ok, RedirectUri} =
  oidcc:create_redirect_url(myapp_oidcc_config_provider,
                            <<"client_id">>,
                            <<"client_secret">>
                            #{redirect_uri: <<"https://example.com/callback"}),

%% Redirect user to `RedirectUri`

%% Retrieve `code` query / form param from redirect back

%% Exchange code for token
{ok, Token} =
  oidcc:retrieve_token(AuthCode,
                       myapp_oidcc_config_provider,
                       <<"client_id">>,
                       <<"client_secret">>,
                       #{redirect_uri => <<"https://example.com/callback">>}),

%% Load userinfo for token
{ok, Claims} =
  oidcc:retrieve_userinfo(Token,
                          myapp_oidcc_config_provider,
                          <<"client_id">>,
                          <<"client_secret">>,
                          #{}),

%% Load introspection for access token
{ok, Introspection} =
  oidcc:introspect_token(Token,
                         myapp_oidcc_config_provider,
                         <<"client_id">>,
                         <<"client_secret">>,
                         #{}),

%% Refresh token when it expires
{ok, RefreshedToken} =
  oidcc:refresh_token(Token,
                      myapp_oidcc_config_provider,
                      <<"client_id">>,
                      <<"client_secret">>,
                      #{}).
```

for more details, see https://hexdocs.pm/oidcc/oidcc.html

### Elixir

```elixir
# Create redirect URI for authorization
{:ok, redirect_uri} =
  Oidcc.create_redirect_url(
    Myapp.OidccConfigProvider,
    "client_id",
    "client_secret",
    %{redirect_uri: "https://example.com/callback"}
  )

# Redirect user to `redirect_uri`

# Retrieve `code` query / form param from redirect back

# Exchange code for token
{:ok, token} = Oidcc.retrieve_token(
  auth_code,
    Myapp.OidccConfigProvider,
  "client_id",
  "client_secret",
  %{redirect_uri: "https://example.com/callback"}
)

# Load userinfo for token
{:ok, claims} = Oidcc.retrieve_userinfo(
  token,
  Myapp.OidccConfigProvider,
  "client_id",
  "client_secret",
  %{expected_subject: "sub"}
)

# Load introspection for access token
{:ok, introspection} = Oidcc.introspect_token(
  token,
  Myapp.OidccConfigProvider,
  "client_id",
  "client_secret"
)

# Refresh token when it expires
{:ok, refreshed_token} = Oidcc.refresh_token(
  token,
  Myapp.OidccConfigProvider,
  "client_id",
  "client_secret"
)
```

for more details, see https://hexdocs.pm/oidcc/Oidcc.html