# oauth2: an Elixir client for the OAuth 2.0 flows

> oauth2 is an Elixir client library for OAuth 2.0, covering the authorization code, password and client credentials strategies, with pluggable serializers for request and response bodies and a Tesla based HTTP layer you can reconfigure. It sits under the ueberauth organisation, which is why it is a client rather than a full authentication stack.

**ueberauth/oauth2** — An Elixir OAuth 2.0 Client Library

- Repository: https://github.com/ueberauth/oauth2
- Stars: 771 · Forks: 141
- Language: Elixir
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ueberauth-oauth2

## A client, not an authentication stack

oauth2 is described as an Elixir OAuth 2.0 Client Library, and the repository sits under the ueberauth organisation. That placement is the context you need, because it explains the scope. This is the part that speaks to a provider, not the part that issues tokens to your own users.

What the library gives you is a client struct, a set of strategies for the different OAuth 2.0 flows, a mechanism for encoding and decoding request and response bodies, and an HTTP layer you can configure. What it does not give you is user accounts, sessions, or a login screen.

The README lists the implemented strategies as Authorization Code, Password and Client Credentials, and it also shows how to write a fourth for a specific provider. The three listed ones cover the majority of real integrations, and the extension point means an awkward provider does not block you.

The audience is an Elixir developer with a Phoenix or Plug application that needs to talk to an external identity provider or API, and who would otherwise write the authorization URL construction, the token exchange and the retry and error handling by hand.

## The client struct and the token it carries

The usage example shows the shape of the API, and it is the same shape for every strategy.

A client is created with OAuth2.Client.new and a keyword list, and the strategy option is optional because it defaults to OAuth2.Strategy.AuthCode:

```elixir
client = OAuth2.Client.new([
  strategy: OAuth2.Strategy.AuthCode, #default
  client_id: "client_id",
  client_secret: "abc123",
  site: "https://auth.example.com",
  redirect_uri: "https://example.com/auth/callback"
])
```

From there the flow is the one you would write by hand. OAuth2.Client.authorize_url! produces the URL to send the user to, and the README shows the resulting string with the client id, the redirect URI and the response type already encoded. The code that comes back from the provider is then exchanged with OAuth2.Client.get_token!(client, code: "someauthcode"), and the access token is used for resource requests with OAuth2.Client.get!(client, "/api/resource").body.

What the client holds matters, and the README is explicit about it in the client credentials example. The token is stored inside the %OAuth2.Client{} struct as client.token, which is an %OAuth2.AccessToken{} struct, and the raw value is client.token.access_token.

The refresh path is the same shape with a different strategy. The README takes client.token.refresh_token, builds a second client with strategy: OAuth2.Strategy.Refresh and params carrying the refresh token, and calls get_token! on that client to get a new %OAuth2.AccessToken{}. The pattern of copying a token into a new client is worth noticing, because it is how you keep a refresh in flight without mutating the client your request code holds.

## Serializers for the content types providers actually return

The library can be configured to handle encoding and decoding of requests and responses automatically, based on the accept and the content-type headers.

If you need to handle various MIME types, you register a serializer for each one:

```elixir
OAuth2.Client.put_serializer(client, "application/vnd.api+json", Jason)
OAuth2.Client.put_serializer(client, "application/xml", MyApp.Parsers.XML)
```

The contract for a serializer module is small, and the README states it plainly: the modules are expected to export encode!/1 and decode!/1. That is a two function module, which is a low bar for a provider that returns something unusual.

The example module shows the shape:

```elixir
defmodule MyApp.Parsers.XML do
  def encode!(data), do: # ...
  def decode!(binary), do: # ...
end
```

This is the part of the library that decides whether a provider is easy or painful. A provider that answers in JSON works out of the box with Jason. A provider that answers in a vendor specific JSON media type, as the application/vnd.api+json example suggests, needs a registration. A provider that answers in XML needs a module like the one above.

The detail to keep in mind is that decoding is driven by the content type, so a provider that changes its answer format, or answers with an error document in a different format from the success case, is a case where the registration list decides whether you get a struct or a parse error.

## Tesla underneath and the adapter you can swap

The HTTP layer is Tesla, and the default adapter is Httpc, which the README explains comes out of the box with every Erlang instance, while noting that you can easily change it to something better.

Swapping it is a configuration line:

```elixir
config :oauth2, adapter: Tesla.Adapter.Mint
```

There is a second configuration point in the same area, which is the middleware list:

```elixir
config :oauth2, middleware: [
  Tesla.Middleware.Retry,
  {Tesla.Middleware.Fuse, name: :example}
]
```

That is where a resilience policy goes. The retry middleware handles a provider that drops a connection mid token exchange, and the fuse middleware is the circuit breaker pattern, which matters more for an OAuth provider than it does for an ordinary API because a failing identity provider takes your whole sign in flow down with it.

So the transport story is a deliberate one. The library does not implement HTTP, it delegates to Tesla, which means the connection pooling, the retry policy, the timeouts and the instrumentation all come from the ecosystem rather than from this package, and the config surface is a single place to set them.

One note for anyone reading the dependency list. The install snippet in the README adds hackney with a comment about depending on which Tesla adapter you use, so the HTTP client you depend on is a decision you make, not one the library forces.

## Writing a strategy for a provider that does not fit

The GitHub example is the most useful part of the README, because it shows what a custom strategy actually costs.

A strategy module uses OAuth2.Strategy, defines a client function that calls OAuth2.Client.new with the provider's URLs, including separate authorize_url and token_url values, and registers a serializer. It then implements the two callbacks the behaviour needs, authorize_url and get_token, and delegates to an existing strategy where that is enough. In the example, authorize_url delegates to OAuth2.Strategy.AuthCode.authorize_url and get_token sets an accept header before delegating to OAuth2.Strategy.AuthCode.get_token.

The public API of a strategy is small: an authorize_url!/0 that returns the URL, and a get_token!/3 that takes params, headers and opts, with the README noting that options can be passed to the underlying http library through the opts parameter.

The value of this pattern is that the provider specific bits, the URLs, the client credentials from the environment, the scope, the accept header and the serializer, live in one small module, while the protocol mechanics live in the library. The example reads the GitHub client id and secret from System.get_env, which is the right shape for a deployment.

Using it afterwards is unremarkable. GitHub.authorize_url! generates the URL, your callback route captures the code, GitHub.get_token!(code: code) exchanges it, and OAuth2.Client.get!(client, "/user").body reads the resource.

## Debug mode, error handling and the release gap

There is a configuration switch for the case where you need to see what the provider actually sent back, and it is one line:

```elixir
config :oauth2, debug: true
```

The README frames it as sometimes being handy to see what is coming back from the response when getting a token, and it is the switch to reach for when a token request fails and the error body is opaque.

The error handling shape is worth reading too, because it is a contract rather than an exception. The usage example shows a case on OAuth2.Client.get that matches a successful %OAuth2.Response{body: user}, an error response with a status code of 401 and a body, and a transport level %OAuth2.Error{reason: reason}. The first is an HTTP error with a payload, the second is a request that never produced a response.

That distinction is the practical value. A provider that returns 401 with a JSON error body and a provider whose DNS failed are different failures, and a library that models them separately saves you from reading exception text to find out which one you have.

On upkeep, the release history is worth noting. v2.0.1 is dated 2022-06-20 and v2.1.0 is dated 2023-01-02, and then the next release, v2.1.1, is dated 2026-06-03, which is also the date of the last push to the repository. So the library was effectively stable for three years and then moved. The dependency constraint in the install snippet is ~> 2.0, which will pick up the 2.1 line, and the CHANGELOG.md in the repository is where the changes between those points are recorded.

## Conclusion

Adopt oauth2 if you are building an Elixir service that talks to an OAuth 2.0 provider and want a typed client with a strategy abstraction rather than hand rolled HTTP calls. Do not adopt it as a complete authentication solution, since it is a client for talking to a provider rather than a server issuing tokens, and do not expect it to solve session handling or user accounts. Verify three things first: which strategy your provider actually implements, since the README lists authorization code, password and client credentials and the GitHub example shows how a fourth is written; the HTTP adapter, because the default is Httpc and the README suggests swapping in something better, with Tesla.Adapter.Mint given as the alternative; and the content types you will receive, since a provider that answers with a format you have not registered a serializer for is a case the library cannot decode for you. The licence is MIT, the last push was on 2026-06-03, and the newest release is v2.1.1 from the same day.

## FAQ

### What is the oauth2 Elixir library?

It is an OAuth 2.0 client library for Elixir, published as the oauth2 package on Hex and developed under the ueberauth organisation. It provides a client struct, pluggable strategies for the OAuth flows, body serializers and a configurable Tesla based HTTP layer.

### Which OAuth 2.0 flows does oauth2 implement?

The README lists Authorization Code, Password and Client Credentials as the current implemented strategies, and it shows how to write a custom strategy, for example one for GitHub, by using OAuth2.Strategy and implementing the authorize_url and get_token callbacks.

### How do I configure the HTTP client used by oauth2?

The library uses Tesla, with Httpc as the default adapter, and the adapter can be changed with config :oauth2, adapter: Tesla.Adapter.Mint. Tesla middleware can be added the same way, for example a retry middleware and a fuse middleware for resilience against a failing provider.

### How do I register a serializer for a non JSON response?

Call OAuth2.Client.put_serializer with the MIME type and a module that exports encode!/1 and decode!/1, for example OAuth2.Client.put_serializer(client, "application/xml", MyApp.Parsers.XML). The library then encodes and decodes bodies automatically based on the accept and content-type headers.

### Where is the access token stored in oauth2?

Inside the client struct, as client.token, which holds an %OAuth2.AccessToken{} struct, and the raw value is client.token.access_token. For a refresh you build a second client with the OAuth2.Strategy.Refresh strategy and the refresh token in its params, then call get_token! on that client.

## Sources

- [Issues](https://github.com/ueberauth/oauth2/issues)
- [License: MIT](https://github.com/ueberauth/oauth2/blob/master/LICENSE)
- [README](https://github.com/ueberauth/oauth2/blob/master/README.md)
- [Releases](https://github.com/ueberauth/oauth2/releases)
- [ueberauth/oauth2 on GitHub](https://github.com/ueberauth/oauth2)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ueberauth-oauth2
