DEV Community

Cover image for Implementing OpenID Connect on the BEAM
Jonatan Männchen
Jonatan Männchen

Posted on

Implementing OpenID Connect on the BEAM

Unlock the Power of OpenID Connect on the BEAM

In today's digital world, implementing secure and efficient authentication systems is more critical than ever. OpenID Connect has emerged as a powerful protocol, offering seamless and secure login experiences for applications. I recently delved deep into this topic during my talk at Code BEAM San Francisco, titled "Unlock the Power of OpenID Connect on the BEAM."

For those who couldn't attend, I'm excited to share the key insights and developments from that presentation through this series of blog posts. If you're interested in exploring further, you can watch the talk recording and download the slides.

Whether you're new to OpenID Connect or looking to enhance your application's security, these posts aim to provide valuable insights and practical guidance.


Part 2: Implementing OpenID Connect on the BEAM

Background and Motivation

Despite OpenID Connect's critical role in modern authentication, I found myself increasingly frustrated with the existing client implementations available for the BEAM ecosystem. Many libraries lacked comprehensive support for the full OpenID Connect specification, especially concerning advanced security features and compliance requirements. This gap not only complicated our development process but also raised concerns about the security and reliability of the applications we were building for our clients.

Determined to address these shortcomings, I took over the maintenance of the oidcc library, originally developed by Indigo. The library already existed but did not fulfill the requirements I had set. I took over the project and implemented a completely new version to fully comply with the OpenID Connect Core specification and include optional security features crucial for high-stakes applications like banking and healthcare.

Implementing OpenID Connect on the BEAM

With the understanding of OpenID Connect's fundamentals and its security features, the next step is integrating it into applications built on the BEAM—the Erlang virtual machine that powers languages like Erlang and Elixir. Implementing OpenID Connect on the BEAM allows developers to leverage the concurrency and fault tolerance of the platform while providing secure authentication mechanisms in their applications.

Challenges with Existing Libraries:

  • Incomplete Implementations: Existing libraries often lacked full support for the OpenID Connect specification, implementing only basic features necessary for simple authentication flows.
  • Missing Security Features: Optional but crucial security features were frequently absent, which are essential for applications handling sensitive data.
  • Lack of Compliance and Certification: Without full compliance and certification, there was a lack of trust and assurance in the reliability and security of these libraries.

Introducing oidcc

To address the need for a comprehensive and secure solution, I developed a completely new version of oidcc, a fully compliant OpenID Connect client library designed specifically for Erlang and Elixir applications running on the BEAM.

What Is oidcc?

oidcc is an OpenID Connect client library that aims to provide:

  • Complete Support for OpenID Connect Core Specification: Implements all mandatory features and many optional ones outlined in the OpenID Connect standard, ensuring broad compatibility with identity providers.
  • Advanced Security Features: Includes support for optional security mechanisms such as PKCE, various client authentication methods, token validation, and more.
  • Compliance and Certification: Designed to meet the compliance requirements set by the OpenID Foundation, providing confidence in its reliability and security.
  • Ease of Use: Focuses on simplicity and developer-friendliness, making it straightforward to integrate into existing applications.
  • Erlang and Elixir Support: Compatible with both Erlang and Elixir, offering flexibility for developers working in either language.

Key Features of oidcc

  • Full OpenID Connect Core Implementation: Supports all standard authentication flows, including the Authorization Code Flow, Implicit Flow, Hybrid Flow, and more.
  • Security by Default: Enforces best security practices out of the box, reducing the risk of misconfiguration.
  • Dynamic Provider Configuration: Utilizes the OpenID Connect discovery mechanism to dynamically retrieve provider configurations.
  • Token Management: Provides robust token handling, including validation, refreshing, and revocation support.
  • Pluggable Architecture: Designed to be extensible, allowing developers to customize and extend functionality as needed.
  • Comprehensive Documentation and Examples: Accompanied by detailed documentation and practical examples.

Companion Libraries

To facilitate seamless integration with popular web frameworks and tools in the BEAM ecosystem, oidcc includes several companion libraries:

  • oidcc_cowboy: Integration with Cowboy, a small, fast, and modern HTTP server for Erlang/OTP.
  • oidcc_plug: Support for Plug, allowing easy integration with Phoenix.
  • phx_gen_oidcc: A Phoenix generator that helps set up OpenID Connect authentication.
  • ueberauth_oidcc: Integration with Überauth, enabling developers to add OpenID Connect strategies to their authentication pipeline.

Why Choose oidcc?

  • Security and Compliance: Adheres strictly to the OpenID Connect specifications and incorporates advanced security features.
  • Community and Support: Hosted under the Erlang Ecosystem Foundation's GitHub organization, benefiting from community contributions and oversight.
  • Flexibility: Offers the flexibility to meet requirements for both simple and complex applications.
  • Ease of Integration: Companion libraries and comprehensive documentation simplify integration.

Installation and Setup

Integrating oidcc into your BEAM applications is straightforward, whether you're using Elixir or Erlang.

Installing oidcc

Add oidcc and its companion libraries to your project's dependencies.

For Elixir Projects (mix.exs):
defmodule MyApp.MixProject do
  use Mix.Project

  # ...

  defp deps do
    [
      {:oidcc, "~> 3.1"},           # Core library
      {:oidcc_plug, "~> 0.1.1"},    # Plug/Phoenix integration
      {:ueberauth_oidcc, "~> 0.3.1"} # Überauth integration (optional)
    ]
  end
end
Enter fullscreen mode Exit fullscreen mode

Run mix deps.get to fetch the dependencies.

For Erlang Projects (rebar.config):
{deps, [
    {oidcc, "~> 3.1"},             % Core library
    {oidcc_cowboy, "~> 3.0"}       % Cowboy integration
]}.
Enter fullscreen mode Exit fullscreen mode

Run rebar3 get-deps to fetch the dependencies.

Configuring Provider Introspection

Start a provider configuration worker to communicate with your OpenID Connect provider.

Elixir Example:
{:ok, _pid} =
  Oidcc.ProviderConfiguration.Worker.start_link(%{
    issuer: "https://example.com",
    name: MyApp.OidcProvider
  })
Enter fullscreen mode Exit fullscreen mode
Erlang Example:
{ok, _Pid} =
    oidcc_provider_configuration_worker:start_link(#{
        issuer => <<"https://example.com">>,
        name => {local, myapp_oidc_provider}
    }),
Enter fullscreen mode Exit fullscreen mode

Implementing Authentication

Elixir Example:
# Define the callback URI
callback_uri = "https://example.com/auth/callback"

# Create the redirect URI
{:ok, redirect_uri} =
  Oidcc.create_redirect_url(
    MyApp.OidcProvider,
    client_id,
    client_secret,
    %{redirect_uri: callback_uri}
  )

# Redirect the user to `redirect_uri`

# After authentication, exchange the authorization code for tokens
{:ok, token} =
  Oidcc.retrieve_token(
    auth_code,
    MyApp.OidcProvider,
    client_id,
    client_secret,
    %{redirect_uri: callback_uri}
  )
Enter fullscreen mode Exit fullscreen mode

Integrating with Web Frameworks

Using Cowboy (Erlang):
% Setup options
OidccCowboyOpts = #{
    provider => myapp_oidc_provider,
    client_id => <<"your_client_id">>,
    client_secret => <<"your_client_secret">>,
    redirect_uri => <<"http://example.com/auth/callback">>
},

% Success handler
OidccCowboyCallbackOpts =
    maps:merge(OidccCowboyOpts, #{
        handle_success => fun(Req, _Token, #{<<"sub">> := Subject}) ->
            cowboy_req:reply(
                200, #{}, [<<"Hello ">>, Subject, <<"!">>], Req
            )
        end
    }),

% Register routes
Dispatch = cowboy_router:compile([
    {'_', [
        {"/auth/init", oidcc_cowboy_authorize, OidccCowboyOpts},
        {"/auth/callback", oidcc_cowboy_callback, OidccCowboyCallbackOpts}
    ]}
]),

% Start the server
{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{
    env => #{dispatch => Dispatch}
}),
Enter fullscreen mode Exit fullscreen mode
Using Plug/Phoenix (Elixir):
defmodule MyAppWeb.OidcController do
  use MyAppWeb, :controller
  alias Oidcc.Plug.{Authorize, AuthorizationCallback}

  plug Authorize,
       [
         provider: MyApp.OidcProvider,
         client_id: "your_client_id",
         client_secret: "your_client_secret",
         redirect_uri: &__MODULE__.callback_uri/0
       ]
       when action in [:authorize]

  plug AuthorizationCallback,
       [
         provider: MyApp.OidcProvider,
         client_id: "your_client_id",
         client_secret: "your_client_secret",
         redirect_uri: &__MODULE__.callback_uri/0
       ]
       when action in [:callback]

  def callback_uri, do: url(~p"/oidc/callback")

  def authorize(conn, _params), do: conn

  def callback(
        %Plug.Conn{
          private: %{AuthorizationCallback => result}
        } = conn,
        _params
      ) do
    case result do
      {:ok, {_token, userinfo}} ->
        conn
        |> put_session("oidc_claims", userinfo)
        |> redirect(to: "/")

      {:error, reason} ->
        conn
        |> put_status(400)
        |> render(:error, reason: reason)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Retrieving User Information and Introspection

Elixir Example:
# Retrieve user information
{:ok, claims} =
  Oidcc.retrieve_userinfo(
    token,
    MyApp.OidcProvider,
    client_id,
    client_secret,
    %{}
  )

# Introspect the access token
{:ok, introspection} =
  Oidcc.introspect_token(
    token,
    MyApp.OidcProvider,
    client_id,
    client_secret
  )
Enter fullscreen mode Exit fullscreen mode

Refreshing Tokens

Elixir Example:
# Refresh the access token
{:ok, refreshed_token} =
  Oidcc.refresh_token(
    token,
    MyApp.OidcProvider,
    client_id,
    client_secret
  )
Enter fullscreen mode Exit fullscreen mode

API Token Validation

Ensuring the validity of API tokens is essential for securing your application's endpoints and protecting sensitive resources. The oidcc library simplifies this task by offering robust support for API token validation through multiple methods.

Library Support for API Token Validation

The oidcc library provides several ways to validate tokens received from clients:

  1. JWT Verification: If your OpenID Connect provider issues access tokens as JWTs, you can perform local validation by verifying the token's signature using the provider's public keys and checking claims like issuer (iss), audience (aud), and expiration time (exp).
  2. UserInfo Endpoint: Validate tokens by making a request to the UserInfo endpoint provided by the OpenID Connect issuer. A successful response confirms the token's validity and retrieves up-to-date user information.
  3. Token Introspection: Use the introspection endpoint to check a token's active status and retrieve associated metadata, useful for opaque tokens that cannot be validated locally.

Middleware Integration

To streamline token validation, oidcc includes middleware components:

  • Plug Middleware: In Elixir applications, integrate the provided Plug middleware into your pipeline to handle token extraction and validation for incoming requests.
  • Cowboy Middleware: For Erlang applications using Cowboy, the library offers middleware that integrates token validation into your request handling.

By leveraging these validation methods and middleware components, you can enhance your application's security posture.

What's Next for oidcc

The development of oidcc is an ongoing effort aimed at providing a comprehensive and secure OpenID Connect client library for the BEAM ecosystem. Looking ahead, several exciting developments and enhancements are planned to further improve the library and its utility in various applications.

Upcoming Features

  • Completion of FAPI 2.0 Certification: Work is underway to achieve full certification for the Financial-grade API (FAPI) 2.0 standards. This certification ensures that oidcc meets the stringent security and interoperability requirements necessary for applications in the financial sector and other high-security environments. Special thanks to Paul Swartz for his significant contributions to this effort.
    Update: FAPI 2.0 is implemented, pending certification.

  • Independent Security Review: An independent security audit is being conducted to thoroughly assess oidcc for potential vulnerabilities and to validate its security features. This review is made possible thanks to a collaboration with Erlang Solutions, who are providing their expertise to ensure the library's robustness.
    Update: The review has been performed by SAFE and all findings have been remediated.

  • Additional Companion Libraries: Plans are in place to develop companion libraries for other frameworks and tools within the BEAM ecosystem, such as integrating with the Ash Framework.

  • Support for Additional OpenID Protocols: Enhancements are being made to support more OpenID Connect protocols, such as Single Logout (SLO) and Self-Sovereign Identity (SSI). Implementing SLO will allow users to log out of multiple applications simultaneously, improving security and user experience.

  • Enhanced Documentation and Examples: Ongoing efforts to improve the documentation, provide more comprehensive examples, and create tutorials will make it easier for developers to adopt and implement oidcc in their projects.

How You Can Help

The continued success of oidcc relies on the support and involvement of the community. Here are several ways you can contribute:

Vote for New Features

  • GitHub Discussions: We've posted a list of potential extensions and new standards to implement on our GitHub Discussions page. If there's a feature you'd like to see, please give it an upvote. If it's not listed, feel free to create a new discussion thread. Your input helps us prioritize developments based on community interest.

Integrate oidcc into Your Projects

  • Library Developers: If you're developing an authentication library that could benefit from oidcc, we'd be delighted to support its integration. We're happy to offer assistance to ensure a smooth implementation.

Try oidcc and Provide Feedback

  • OpenID Connect Users: If you're using OpenID Connect in your applications, try out oidcc and let us know what you think. Your feedback is invaluable and helps us improve the library to better meet the community's needs.

Get Involved with the EEF

Support the EEF as a Company

  • Corporate Sponsorship: If your company benefits from the BEAM ecosystem, consider sponsoring the EEF. Your support enables initiatives like oidcc and helps foster the development of the entire ecosystem, including community growth, educational resources, and collaborative projects that benefit everyone.

Top comments (0)