<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 Test Suite](https://github.com/erlef/oidcc_conformance).
<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"/>
<picture style="margin-right: 15px; float: left;">
<source
media="(prefers-color-scheme: dark)"
srcset="https://security-audit-logo.s3.eu-central-1.amazonaws.com/image_safe_logo_dark.png"
width="170px"
align="left"
/>
<source
media="(prefers-color-scheme: light)"
srcset="https://security-audit-logo.s3.eu-central-1.amazonaws.com/image_safe_logo_light.png"
width="170px"
align="left"
/>
<img
src="https://security-audit-logo.s3.eu-central-1.amazonaws.com/image_safe_logo_light.png"
alt="Security Audit For Erlang and Elixir"
width="170px"
align="left"
/>
</picture>
A security audit was performed by [SAFE-Erlang-Elixir](https://github.com/SAFE-Erlang-Elixir) more info [HERE](https://www.erlang-solutions.com/landings/security-audit-for-erlang-2/).
<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/)
* [Pushed Authorization Requests](https://datatracker.ietf.org/doc/html/rfc9126)
* [Authorization Server Issuer Identification](https://datatracker.ietf.org/doc/html/rfc9207)
* 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)
* [JWT Secured Authorization Response Mode for OAuth 2.0 (JARM)](https://openid.net/specs/oauth-v2-jarm-final.html)
* [Demonstrating Proof of Possession (DPoP)](https://datatracker.ietf.org/doc/html/rfc9449)
* [OAuth 2 Purpose Request Parameter](https://cdn.connectid.com.au/specifications/oauth2-purpose-01.html)
* Profiles
* [FAPI 2.0 Security Profile](https://openid.bitbucket.io/fapi/fapi-2_0-security-profile.html)
* [FAPI 2.0 Message Signing](https://openid.bitbucket.io/fapi/fapi-2_0-message-signing.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