OpenIDConnect (OpenID Connect v1.0.0)
View SourceOpenID Connect client library for Elixir.
This library provides a complete implementation of the OpenID Connect authentication protocol, which is built on top of OAuth 2.0. It handles the complete authentication flow including:
- Generating authorization URIs for redirecting users to identity providers
- Fetching tokens from the token endpoint
- Verifying JWT ID tokens using provider JWKs (JSON Web Keys)
- Fetching user information from the userinfo endpoint
- Supporting provider logout via end session endpoints
Supported Authentication Flows
The library supports the standard OpenID Connect authentication flows:
- Authorization Code Flow (most common for web applications)
- Implicit Flow (less secure, used for JavaScript applications)
- Hybrid Flow (combines aspects of both)
Supported Identity Providers
This library works with any OpenID Connect compliant provider, including:
- Microsoft Azure AD
- Auth0
- Okta
- Amazon Cognito
- Keycloak
- OneLogin
- HashiCorp Vault
- And many others
Basic Usage
# Step 1: Configure the provider
google_config = %{
discovery_document_uri: "https://accounts.google.com/.well-known/openid-configuration",
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET",
response_type: "code",
scope: "openid email profile"
}
# Step 2: Generate the authorization URI (redirect the user to this URI)
{:ok, uri} = OpenIDConnect.authorization_uri(
google_config,
"https://example.com/auth/callback",
%{state: state_token}
)
# Step 3: Exchange the authorization code for tokens
{:ok, tokens} = OpenIDConnect.fetch_tokens(
google_config,
%{
code: auth_code,
redirect_uri: "https://example.com/auth/callback"
}
)
# Step 4: Verify the ID token
{:ok, claims} = OpenIDConnect.verify(google_config, tokens["id_token"])
# Optional: Fetch additional user information
{:ok, userinfo} = OpenIDConnect.fetch_userinfo(google_config, tokens["access_token"])
See the README for more detailed examples and configuration options.
Summary
Types
OAuth 2.0 Client Identifier valid at the Authorization Server.
OAuth 2.0 Client Secret valid at the Authorization Server.
The configuration of a OpenID provider.
URL to a OpenID Discovery Document endpoint.
JSON Web Token
Redirection URI to which the response will be sent.
OAuth 2.0 Response Type value that determines the authorization processing flow to be used, including what parameters are returned from the endpoints used.
OAuth 2.0 Scope Values that the Client is declaring that it will restrict itself to using.
Functions
Builds an authorization URI for redirecting users to the identity provider.
Builds a URI for ending the user's session with the identity provider.
Fetches authentication tokens from the provider using the token endpoint.
Fetches information about the authenticated user from the userinfo endpoint.
Verifies the validity and authenticity of an OpenID Connect ID token.
Types
@type client_id() :: String.t()
OAuth 2.0 Client Identifier valid at the Authorization Server.
@type client_secret() :: String.t()
OAuth 2.0 Client Secret valid at the Authorization Server.
@type config() :: %{ :discovery_document_uri => discovery_document_uri(), :client_id => client_id(), :client_secret => client_secret(), :response_type => response_type(), :scope => scope(), optional(:leeway) => non_neg_integer() }
The configuration of a OpenID provider.
@type discovery_document_uri() :: String.t()
URL to a OpenID Discovery Document endpoint.
@type jwt() :: String.t()
JSON Web Token
@type redirect_uri() :: String.t()
Redirection URI to which the response will be sent.
This URI MUST exactly match one of the Redirection URI values for the Client pre-registered at the OpenID Provider, with the matching performed as described in Section 6.2.1 of [RFC3986] (Simple String Comparison).
When using this flow, the Redirection URI SHOULD use the https scheme; however, it MAY use the http scheme, provided that the Client Type is confidential, as defined in Section 2.1 of OAuth 2.0, and provided the OP allows the use of http Redirection URIs in this case. The Redirection URI MAY use an alternate scheme, such as one that is intended to identify a callback into a native application.
OAuth 2.0 Response Type value that determines the authorization processing flow to be used, including what parameters are returned from the endpoints used.
OAuth 2.0 Scope Values that the Client is declaring that it will restrict itself to using.
Functions
@spec authorization_uri( config(), redirect_uri :: redirect_uri(), params :: %{optional(atom()) => term()} ) :: {:ok, uri :: String.t()} | {:error, term()}
Builds an authorization URI for redirecting users to the identity provider.
This function creates a URL that starts the OpenID Connect authentication flow by redirecting the user to the provider's authorization endpoint. The endpoint URL is automatically retrieved from the provider's discovery document.
Parameters
config
- The provider configuration mapredirect_uri
- URI to redirect to after authentication (must match one registered with the provider)params
- Optional map of additional query parameters to include in the authorization URI
Common Additional Parameters
state
- Strongly recommended - Random token to prevent CSRF attacksprompt
- Controls the login experience, common values include:"none"
- No interactive prompt, fails if user authentication is required"login"
- Forces the user to enter their credentials even if already logged in"consent"
- Forces the consent screen to be displayed even if previously consented"select_account"
- Prompts the user to select an account
login_hint
- Email address or sub identifier to pre-fill the login formmax_age
- Maximum elapsed time in seconds since last authenticationui_locales
- Preferred languages for the UI, space-separated list of BCP47 tagsacr_values
- Authentication Context Class Reference values
Provider-Specific Parameters
Some providers support additional custom parameters:
- Google:
hd
(hosted domain) to restrict to specific Google Workspace domains - Azure AD:
domain_hint
to skip the home realm discovery page - Many others based on the provider
Returns
{:ok, uri}
- On success, returns the authorization URI string{:error, reason}
- On failure, returns an error tuple with details
Example: Basic Usage
{:ok, uri} = OpenIDConnect.authorization_uri(
google_config,
"https://example.com/auth/callback",
%{state: state_token}
)
Example: With Additional Parameters
{:ok, uri} = OpenIDConnect.authorization_uri(
google_config,
"https://example.com/auth/callback",
%{
state: state_token,
prompt: "login",
login_hint: "user@example.com",
hd: "example.com" # Google-specific parameter
}
)
Security: State Parameter
The state
parameter is critical for preventing cross-site request forgery attacks.
Always include a state parameter with a secure random value and validate it when
the user is redirected back to your application.
Creating a State Token
state_token = Base.encode64(:crypto.strong_rand_bytes(32), padding: false)
# Store the token in your session
conn = Plug.Conn.put_session(conn, :oidc_state_token, state_token)
# Include it in the authorization URI
{:ok, uri} = OpenIDConnect.authorization_uri(
google_config,
redirect_uri,
%{state: state_token}
)
Validating the State Token
When the provider redirects back to your application, verify the state parameter:
stored_token = Plug.Conn.get_session(conn, :oidc_state_token)
received_token = params["state"]
if stored_token && Plug.Crypto.secure_compare(stored_token, received_token) do
# State token is valid, proceed with token exchange
else
# Invalid state token, reject the request
end
For more details on state tokens, see Google's Documentation.
@spec end_session_uri(config(), params :: %{optional(atom()) => term()}) :: {:ok, uri :: String.t()} | {:error, term()}
Builds a URI for ending the user's session with the identity provider.
This function creates a URL for OpenID Connect RP-Initiated Logout, allowing your application to sign the user out of the identity provider's session. The endpoint URL is automatically retrieved from the provider's discovery document.
Parameters
config
- The provider configuration mapparams
- Optional map of additional query parameters for the end session URI
Common Additional Parameters
id_token_hint
- The ID token previously issued to the user (strongly recommended)post_logout_redirect_uri
- URI to redirect to after logout (must be registered with provider)state
- Opaque value for maintaining state between logout request and callback
Provider Requirements
Different providers have different requirements for logout:
- Azure AD: Requires
post_logout_redirect_uri
and may acceptid_token_hint
- Auth0: Requires
client_id
andreturnTo
(instead ofpost_logout_redirect_uri
) - Cognito: Requires
client_id
andlogout_uri
(their name for redirect URI) - Okta: Requires
id_token_hint
and acceptspost_logout_redirect_uri
- Keycloak: Accepts
id_token_hint
andpost_logout_redirect_uri
Always consult your provider's documentation for specific requirements.
Returns
{:ok, uri}
- On success, returns the end session URI string{:error, :endpoint_not_set}
- If the provider doesn't support RP-Initiated Logout{:error, reason}
- On other failures, returns an error tuple with details
Provider Support Note
Not all providers support RP-Initiated Logout. If the provider's discovery document
doesn't include an end_session_endpoint
, this function will return
{:error, :endpoint_not_set}
. In such cases, you'll need to implement session
termination in your application only.
Example: Azure AD Logout
{:ok, logout_uri} = OpenIDConnect.end_session_uri(
azure_config,
%{
post_logout_redirect_uri: "https://example.com/logged-out"
}
)
# Redirect the user to the logout_uri
redirect(conn, external: logout_uri)
Example: With ID Token Hint
{:ok, logout_uri} = OpenIDConnect.end_session_uri(
okta_config,
%{
id_token_hint: id_token,
post_logout_redirect_uri: "https://example.com/logged-out"
}
)
For more details, see the OpenID Connect RP-Initiated Logout Specification.
@spec fetch_tokens(config(), params :: %{optional(atom()) => term()}) :: {:ok, response :: map()} | {:error, term()}
Fetches authentication tokens from the provider using the token endpoint.
This function exchanges the authorization code or refresh token for access and ID tokens from the identity provider's token endpoint. The endpoint URL is automatically retrieved from the provider's discovery document.
Parameters
config
- The provider configuration mapparams
- A map of parameters to send to the token endpoint
Common Parameters
The params map should include different fields depending on the grant type:
Authorization Code Grant (most common)
%{
code: "AUTHORIZATION_CODE_FROM_CALLBACK",
redirect_uri: "https://example.com/callback", # Must match the original redirect_uri
grant_type: "authorization_code" # Optional, defaults to "authorization_code"
}
Refresh Token Grant
%{
refresh_token: "REFRESH_TOKEN",
grant_type: "refresh_token"
}
Returns
{:ok, tokens}
- On successful token exchange, returns a map containing at minimum:"access_token"
- The OAuth 2.0 access token"token_type"
- The token type (typically "Bearer")"expires_in"
- Token lifetime in seconds"id_token"
- The OpenID Connect ID token (JWT)"refresh_token"
- Optional refresh token for obtaining new access tokens
{:error, reason}
- On failure, returns an error tuple with reason
Example
{:ok, tokens} = OpenIDConnect.fetch_tokens(
google_config,
%{
code: params["code"],
redirect_uri: "https://example.com/auth/callback"
}
)
# Access the tokens
access_token = tokens["access_token"]
id_token = tokens["id_token"]
refresh_token = tokens["refresh_token"]
For more details, see the OpenID Connect spec.
Fetches information about the authenticated user from the userinfo endpoint.
This function retrieves additional claims about the end-user from the provider's userinfo endpoint using the access token. This can be useful when you need user attributes that weren't included in the ID token claims.
Parameters
config
- The provider configuration mapaccess_token
- The OAuth 2.0 access token obtained fromfetch_tokens/2
Returns
{:ok, userinfo}
- On success, returns a map of user information{:error, reason}
- On failure, returns an error tuple with details
Userinfo Claims
The returned claims will depend on the scopes requested during authorization, but typically include:
"sub"
- Subject identifier (unique user ID)"name"
- User's full name (if "profile" scope)"given_name"
- First name (if "profile" scope)"family_name"
- Last name (if "profile" scope)"email"
- Email address (if "email" scope)"email_verified"
- Boolean indicating if email is verified (if "email" scope)"picture"
- URL to profile picture (if "profile" scope)
Example
{:ok, userinfo} = OpenIDConnect.fetch_userinfo(google_config, access_token)
# Access user information
user_id = userinfo["sub"]
email = userinfo["email"]
name = userinfo["name"]
Security Note
The userinfo endpoint requires a valid access token. The token must have appropriate scopes (typically "openid" and others like "profile" or "email").
For more details, see the OpenID Connect UserInfo Endpoint.
Verifies the validity and authenticity of an OpenID Connect ID token.
This security-critical function verifies that:
- The token's signature is valid and was signed by the provider's key
- The token has not expired (checking the "exp" claim)
- The token is intended for your application (checking the "aud" claim)
Parameters
config
- The provider configuration mapjwt
- The ID token string (a JSON Web Token) from the tokens response
Returns
{:ok, claims}
- On successful verification, returns the decoded claims from the token{:error, reason}
- On verification failure, returns an error tuple with details
Verification Process
- The signature is verified using the provider's JSON Web Keys (JWKs), which are fetched from the provider's JWKs endpoint listed in the discovery document.
- The "exp" (expiration) claim is checked to ensure the token has not expired. A configurable leeway (default: 30 seconds) is allowed to account for clock skew.
- The "aud" (audience) claim is verified to ensure the token is intended for your application, as identified by your client_id.
Example
{:ok, claims} = OpenIDConnect.verify(google_config, id_token)
# Common claims you might find in the response:
user_id = claims["sub"] # Unique user identifier
email = claims["email"] # User's email (if in scope)
name = claims["name"] # User's name (if in scope)
picture = claims["picture"] # URL to user's profile picture (if available)
issuer = claims["iss"] # Identifies the token issuer
Security Warning
Always verify tokens before trusting their contents. Never use token data for authentication purposes without verification, as tokens could be forged or tampered with.