OpenIDConnect

Client library for consuming and working with OpenID Connect Providers

OpenIDConnect is built and maintained by DockYard, contact us for expert Elixir and Phoenix consulting.

Installation

Available in Hex, the package can be installed as:

Add openid_connect to your list of dependencies in mix.exs:

def deps do
  [{:openid_connect, "~> 1.0.1"}]
end

Getting Started

Configuration

This library works with any OpenID Connect provider through a standard configuration map. You'll need to create a configuration map for each provider you want to work with:

google_config = %{
  discovery_document_uri: "https://accounts.google.com/.well-known/openid-configuration",
  client_id: "CLIENT_ID",
  client_secret: "CLIENT_SECRET",
  redirect_uri: "https://example.com/session",
  response_type: "code",
  scope: "openid email profile"
}

The configuration requires:

Additional optional configuration:

Most major OAuth2 providers support OpenID Connect, including:

For a full list, see major OpenID Connect providers.

Usage

Implementing OpenID Connect authentication in your app involves these steps:

  1. Generate an authorization URI and redirect the user to it
  2. Handle the callback from the provider after successful authentication
  3. Exchange the authorization code for tokens
  4. Verify the ID token
  5. Use the claims to establish a user session

1. Generate the Authorization URI

Create a URL that will redirect the user to the provider's login page:

{:ok, uri} = OpenIDConnect.authorization_uri(
  google_config,
  "https://example.com/auth/callback",
  %{state: state_token}
)

You should:

2. Handle the Callback from the Provider

After the user logs in, the provider redirects back to your redirect_uri with an authorization code and the state token you provided:

# In your callback controller/handler
def callback(conn, params) do
  # First verify that the state parameter matches the one we stored
  stored_state = get_session(conn, :state_token)
  received_state = params["state"]
  
  if Plug.Crypto.secure_compare(stored_state, received_state) do
    # State is valid, proceed with code exchange
    handle_valid_callback(conn, params)
  else
    # State token doesn't match, reject the request
    conn
    |> put_status(401)
    |> render("error.html", error: "Invalid state parameter")
  end
end

3. Exchange the Authorization Code for Tokens

Exchange the code for ID and access tokens:

{:ok, tokens} = OpenIDConnect.fetch_tokens(google_config, %{
  code: params["code"],
  redirect_uri: "https://example.com/auth/callback"  # Must match original redirect_uri
})

# tokens will contain:
# %{
#   "access_token" => "ya29.a0AfB_...",
#   "id_token" => "eyJhbGciOiJSUzI1...",
#   "expires_in" => 3599,
#   "token_type" => "Bearer",
#   "refresh_token" => "1//03s..." (if requested)
# }

4. Verify the ID Token

The ID token is a JWT containing claims about the user. Always verify it before trusting its contents:

{:ok, claims} = OpenIDConnect.verify(google_config, tokens["id_token"])

# claims will contain user information like:
# %{
#   "sub" => "10865933565....",  # Unique user identifier
#   "email" => "user@example.com",
#   "email_verified" => true,
#   "name" => "User Name",
#   "picture" => "https://...",
#   "given_name" => "User",
#   "family_name" => "Name",
#   "locale" => "en",
#   "iat" => 1585662674,  # Issued at timestamp
#   "exp" => 1585666274,  # Expiration timestamp
#   "aud" => "YOUR_CLIENT_ID",
#   "iss" => "https://accounts.google.com"
# }

5. Use the Claims to Establish a User Session

Use the verified claims to identify your user and establish a session:

# The "sub" claim is the unique identifier for the user
user_id = claims["sub"]

# Find or create a user in your system
user = find_or_create_user(user_id, claims)

# Establish a session for this user
conn
|> put_session(:user_id, user.id)
|> redirect(to: "/dashboard")

Optional: Fetch Additional User Information

Some providers allow you to fetch additional user information using the access token:

{:ok, userinfo} = OpenIDConnect.fetch_userinfo(google_config, tokens["access_token"])

Ending a User Session

Some providers support log out via an end session endpoint:

{:ok, logout_uri} = OpenIDConnect.end_session_uri(
  google_config,
  %{id_token_hint: id_token, post_logout_redirect_uri: "https://example.com/logout/callback"}
)

# Redirect the user to the logout_uri
redirect(conn, external: logout_uri)

Key Security Considerations:

  1. Always validate the state token
  2. Store secrets (client ID, client secret) in environment variables
  3. Verify the ID token before trusting its contents
  4. Use HTTPS for all redirect URIs
  5. Consider token storage carefully - sessions are usually appropriate
  6. Clear tokens when logging out

Authors

We are very thankful for the many contributors

Versioning

This library follows Semantic Versioning

Looking for help with your Elixir project?

At DockYard we are ready to help you build your next Elixir project. We have a unique expertise in Elixir and Phoenix development that is unmatched. Get in touch!

At DockYard we love Elixir! You can read our Elixir blog posts or come visit us at The Boston Elixir Meetup that we organize.

Want to help?

Please do! We are always looking to improve this library. Please see our Contribution Guidelines on how to properly submit issues and pull requests.

Sponsors

In addition to our contributors, this library receives support and maintenance from TV Labs

TV Labs Logo

Legal

DockYard, Inc. © 2018

@dockyard

Licensed under the MIT license