ueberauth/oauth2

repository·master·Indexed 21 days ago

https://github.com/ueberauth/oauth2

An Elixir library for implementing OAuth 2.0 clients. It supports multiple flows including Authorization Code, Password, and Client Credentials, and allows for the implementation of custom strategies for specific providers. The library utilizes Tesla for HTTP requests and provides tools for managing access tokens, refreshing tokens, and configuring custom serializers for MIME types.

Tokens
7.5K
Snippets
29
Records
35
Agent score
72%

What's inside ueberauth-oauth2

  1. Refresh an access token

    master

    To refresh an expired token, use the OAuth2.Strategy.Refresh strategy and pass the existing refresh_token in the params option.

    # Assume client.token contains the existing refresh_token
    refresh_token = client.token.refresh_token
    
    refresh_client = OAuth2.Client.new([
      strategy: OAuth2.Strategy.Refresh,
      client_id: "client_id",
      client_secret: "abc123",
      site: "https://auth.example.com",
      params: %{"refresh_token" => refresh_token}
    ])
    
    # Obtain the new access token
    refresh_client = OAuth2.Client.get_token!(refresh_client)
  2. Install the oauth2 library

    master

    Add oauth2 to your mix.exs dependencies. You should also include an HTTP client dependency like hackney depending on which Tesla adapter you intend to use.

    # mix.exs
    
    defp deps do
      # Add the dependency
      [
        {:oauth2, "~> 2.0"},
        {:hackney, "~> 1.18"} # depending on what tesla adapter you use
      ]
    end
  3. Use the Client Credentials Flow

    master

    Use OAuth2.Strategy.ClientCredentials for machine-to-machine authentication where no user interaction is required.

    # 1. Initialize client
    client = OAuth2.Client.new([
      strategy: OAuth2.Strategy.ClientCredentials,
      client_id: "client_id",
      client_secret: "abc123",
      site: "https://auth.example.com"
    ])
    
    # 2. Request the token
    client = OAuth2.Client.get_token!(client)
    
    # 3. Access the token
    access_token = client.token.access_token
  4. Use the Authorization Code Flow (AuthCode Strategy)

    master

    The Authorization Code flow is the default strategy. Use it to redirect users to a provider, capture the returned code, and exchange it for an access token.

    # 1. Initialize the client
    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"
    ])
    
    # 2. Generate the authorization URL for redirection
    OAuth2.Client.authorize_url!(client)
    # => "https://auth.example.com/oauth/authorize?client_id=client_id&redirect_uri=https%3A%2F%2Fexample.com%2Fauth%2Fcallback&response_type=code"
    
    # 3. Exchange the code for a token
    client = OAuth2.Client.get_token!(client, code: "someauthcode")
    
    # 4. Use the token to make requests
    resource = OAuth2.Client.get!(client, "/api/resource").body
  5. Implement a custom OAuth2 strategy

    master

    To support a specific provider (like GitHub), create a module that uses OAuth2.Strategy. You must implement the strategy's public API (like authorize_url! and get_token!) and provide callbacks that delegate to the appropriate underlying OAuth2 logic.

    defmodule GitHub do
      use OAuth2.Strategy
    
      # Public API
    
      def client do
        OAuth2.Client.new([
          strategy: __MODULE__,
          client_id: System.get_env("GITHUB_CLIENT_ID"),
          client_secret: System.get_env("GITHUB_CLIENT_SECRET"),
          redirect_uri: "http://myapp.com/auth/callback",
          site: "https://api.github.com",
          authorize_url: "https://github.com/login/oauth/authorize",
          token_url: "https://github.com/login/oauth/access_token"
        ])
        |> OAuth2.Client.put_serializer("application/json", Jason)
      end
    
      def authorize_url! do
        OAuth2.Client.authorize_url!(client(), scope: "user,public_repo")
      end
    
      def get_token!(params \ [], headers \ [], opts \ []) do
        OAuth2.Client.get_token!(client(), params, headers, opts)
      end
    
      # Strategy Callbacks
    
      def authorize_url(client, params) do
        OAuth2.Strategy.AuthCode.authorize_url(client, params)
      end
    
      def get_token(client, params, headers) do
        client
        |> put_header("accept", "application/json")
        |> OAuth2.Strategy.AuthCode.get_token(params, headers)
      end
    end
  6. Configure serializers for MIME types

    master

    You can register custom serializers to handle automatic encoding and decoding of requests and responses based on accept or content-type headers. The registered modules must implement encode!/1 and decode!/1 functions.

    OAuth2.Client.put_serializer(client, "application/vnd.api+json", Jason)
    OAuth2.Client.put_serializer(client, "application/xml", MyApp.Parsers.XML)
    
    # Example of a required module structure:
    defmodule MyApp.Parsers.XML do
      def encode!(data), do: # ...
      def decode!(binary), do: # ...
    end
  7. Configure the HTTP client and Tesla middleware

    master

    The library uses tesla for HTTP requests. By default, it uses the Httpc adapter. You can change the adapter or add custom Tesla middleware via your application configuration.

    # Change the adapter
    config :oauth2, adapter: Tesla.Adapter.Mint
    
    # Add custom Tesla middleware
    config :oauth2, middleware: [
      Tesla.Middleware.Retry,
      {Tesla.Middleware.Fuse, name: :example}
    ]
  8. Understand the OAuth2.Response struct

    master

    The OAuth2.Response struct represents the parsed result of an HTTP response received by an OAuth2.Client. It contains the status code, headers, and the decoded body.

    Fields:

    • status_code: The integer HTTP response status code.
    • headers: A list of {key, value} tuples. Note that all header keys are normalized to lowercase.
    • body: The parsed response body. The type of the body (binary, map, or list) depends on the Content-Type header and the configured serializer.
  9. Use the Resource Owner Password Credentials strategy

    master

    The OAuth2.Strategy.Password module implements the Resource Owner Password Credentials flow (RFC 6749, Section 1.3.3). This strategy allows a client to exchange a user's username and password directly for an access token.

    Warning: This grant type should only be used when there is a high degree of trust between the resource owner and the client (e.g., the client is part of the device operating system) and when other grant types like Authorization Code are not available.

    Key Characteristics

    • No Authorization URL: Unlike the AuthCode strategy, this strategy does not implement authorize_url/2. Attempting to call it will raise an OAuth2.Error.
    • Required Parameters: You must provide both :username and :password in the params keyword list or within the client.params configuration. If either is missing, an OAuth2.Error is raised.
  10. Handle OAuth2 API responses

    master

    When making requests with OAuth2.Client.get/2 or OAuth2.Client.get!/2, you can handle successful responses or catch errors like 401 Unauthorized or general OAuth2 errors.

    # Using the custom GitHub strategy example
    case OAuth2.Client.get(client, "/user") do
      {:ok, %OAuth2.Response{body: user}} ->
        user
      {:error, %OAuth2.Response{status_code: 401, body: body}} ->
        Logger.error("Unauthorized token")
      {:error, %OAuth2.Error{reason: reason}} ->
        Logger.error("Error: #{inspect reason)}")
    end