Gmail and MS365 adapters use host-owned token_callback and user_email
credentials. DripDrop sends with access tokens but does not read OAuth client
secrets, persist refresh tokens, or make refresh requests.
Callback Contract
The callback can be an arity-1 function, {Module, :function}, an arity-0
function, or {Module, :function, args}. Arity-1 callbacks receive the channel
adapter. Return values can be a bare token string, %{access_token: token},
%{"access_token" => token}, %{token: token}, or %{"token" => token}.
Expiration can be expires_at, expires_in, or omitted.
{:ok, %{access_token: "ya29...", expires_at: DateTime.utc_now() |> DateTime.add(3600)}}or an error:
{:error, :revoked}DripDrop caches successful tokens by adapter and provider until expiry. The callback is not invoked while a cached token is valid; tokens without a valid expiry are cached for five minutes.
Hand-Rolled Req Example
defmodule MyApp.GoogleTokens do
def token(_adapter) do
{:ok, %{body: body}} =
Req.post("https://oauth2.googleapis.com/token",
form: [
grant_type: "refresh_token",
refresh_token: System.fetch_env!("GOOGLE_REFRESH_TOKEN"),
client_id: System.fetch_env!("GOOGLE_CLIENT_ID"),
client_secret: System.fetch_env!("GOOGLE_CLIENT_SECRET")
]
)
{:ok, %{access_token: body["access_token"], expires_at: DateTime.add(DateTime.utc_now(), body["expires_in"])}}
end
endTango Example
Tango is a recommended companion library, not a DripDrop dependency.
defmodule MyApp.TangoTokens do
def token(adapter) do
connection_id = adapter.credentials["oauth_connection_id"]
with {:ok, connection} <- Tango.Connection.get_by_external_id(connection_id),
{:ok, token} <- Tango.Connection.access_token(connection) do
{:ok, %{access_token: token.value, expires_at: token.expires_at}}
end
end
endUeberauth Example
Use Ueberauth for login/consent and a host-owned refresh job for token storage. The callback should only fetch the current access token from host storage:
defmodule MyApp.UeberauthTokens do
def token(adapter) do
identity = adapter.credentials["oauth_identity"]
case MyApp.OAuthTokens.current(identity, :google) do
nil -> {:error, :revoked}
token -> {:ok, %{access_token: token.access_token, expires_at: token.expires_at}}
end
end
end