# `Magpie.Auth.TokenServer`
[🔗](https://github.com/alexcassol/magpie/blob/v0.3.1/lib/magpie/auth/token_server.ex#L1)

A `Magpie.Auth.TokenProvider` that keeps an access token fresh.

Holds the current `Magpie.Auth.Token` plus the credentials needed to
renew it, and refreshes it a few minutes before it expires — so requests
never wait on a token that Dropbox is about to reject.

Put it in your supervision tree and hand its name to the client:

    children = [
      {Magpie.Auth.TokenServer,
       name: MyApp.DropboxToken,
       app_key: System.fetch_env!("DROPBOX_APP_KEY"),
       app_secret: System.fetch_env!("DROPBOX_APP_SECRET"),
       refresh_token: System.fetch_env!("DROPBOX_REFRESH_TOKEN")}
    ]

    client = Magpie.Client.new(token_provider: {Magpie.Auth.TokenServer, MyApp.DropboxToken})

The refresh token itself is optional at startup. A server started without
one sits in an *unconfigured* state — calls return
`{:error, %Magpie.Error{summary: "no_refresh_token"}}` until
`set_refresh_token/3` hands it a token. That makes a plain static
supervision tree work even on a fresh install, where the user has not
authorized the app yet; see the [OAuth guide](oauth.html).

## Options

  * `:app_key` — your Dropbox app key (required)
  * `:refresh_token` — the long-lived refresh token. Optional: without it
    the server starts unconfigured, waiting for `set_refresh_token/3`
  * `:app_secret` — your app secret. Required unless `pkce: true`
  * `:pkce` — set to `true` for public apps, which authenticate with the
    app key alone and therefore have no secret
  * `:name` — registered name, so `Magpie.Client.new/1` and your
    supervision tree can refer to the server without carrying its pid
  * `:access_token` / `:expires_at` — an access token you already have,
    to avoid a refresh on the first call. Pass both or neither: a token
    without a known expiry is refreshed right away
  * `:refresh_margin` — how many seconds before expiry a token is
    considered stale (default `300`)
  * `:on_refresh` — 1-arity function called with the new
    `Magpie.Auth.Token` after every successful refresh, e.g. to persist it

## Concurrency

Refreshes happen inside the server, so concurrent callers queue behind a
single HTTP request — a burst of requests can never trigger a burst of
refreshes. When a refresh fails the server keeps its previous state and
replies `{:error, %Magpie.Error{}}` to the caller instead of crashing:
a temporarily unreachable Dropbox should not take your supervision tree
down, and the next call simply tries again.

`set_refresh_token/3` goes through the same serialization: it runs before
or after a refresh, never during one, so a refresh finishing around a
re-authorization can never resurrect the old token.

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `fetch_token`

```elixir
@spec fetch_token(GenServer.server()) ::
  {:ok, String.t()} | {:error, Magpie.Error.t()}
```

Returns the current access token, refreshing it first when it is expired
or within `:refresh_margin` seconds of expiring.

    {:ok, access_token} = Magpie.Auth.TokenServer.fetch_token(MyApp.DropboxToken)

An unconfigured server (no refresh token yet) answers with a
pattern-matchable error instead of calling Dropbox:

    {:error, %Magpie.Error{summary: "no_refresh_token"}} =
      Magpie.Auth.TokenServer.fetch_token(MyApp.DropboxToken)

# `refresh_token`

```elixir
@spec refresh_token(GenServer.server()) ::
  {:ok, String.t()} | {:error, Magpie.Error.t()}
```

Forces a refresh and returns the new access token.

Magpie calls this for you when Dropbox rejects a token as expired; you
rarely need to call it yourself.

# `set_refresh_token`

```elixir
@spec set_refresh_token(GenServer.server(), String.t(), keyword()) :: :ok
```

Stores a (new) refresh token, configuring the server or replacing the
token it holds — the re-authorization case.

Any cached access token is discarded, since it belongs to the previous
authorization. If you already hold a valid pair, seed the cache to save
the first refresh — pass both options or neither:

  * `:access_token` — a currently valid access token
  * `:expires_at` — its absolute expiry, as a `DateTime`

Dropbox is not contacted: the next `fetch_token/1` performs the refresh
(or uses the seeded pair), so this is safe to call from a web request.

    # OAuth callback, right after the code exchange
    {:ok, token} = Magpie.Auth.exchange_code(app_key, code, app_secret: secret)
    :ok = Magpie.Auth.TokenServer.set_refresh_token(MyApp.DropboxToken, token.refresh_token)

    # On boot, from storage — seeded, so no refresh until it nears expiry
    :ok =
      Magpie.Auth.TokenServer.set_refresh_token(MyApp.DropboxToken, stored.refresh_token,
        access_token: stored.access_token,
        expires_at: stored.expires_at
      )

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts the server.

See the module documentation for the supported options.

# `token`

```elixir
@spec token(GenServer.server()) :: Magpie.Auth.Token.t() | nil
```

Returns the whole token currently held by the server, without refreshing.

Handy for inspecting the expiry or persisting the token on shutdown.
Returns `nil` while the server is unconfigured.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
