123 lines
4.8 KiB
Markdown
123 lines
4.8 KiB
Markdown
# OAuth2TokenAgent
|
|
|
|
This package works with the `oauth2` package to manage the automatic renewal of
|
|
tokens before they expire
|
|
|
|
## Installation
|
|
|
|
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
|
|
by adding `oauth2_token_agent` to your list of dependencies in `mix.exs`:
|
|
|
|
```elixir
|
|
def deps do
|
|
[
|
|
{:oauth2_token_agent, "~> 0.1.0"}
|
|
]
|
|
end
|
|
```
|
|
|
|
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
|
|
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
|
|
be found at <https://hexdocs.pm/oauth2_token_agent>.
|
|
|
|
## Usage
|
|
|
|
Create an `OAuth2.Client` instance to use to get the initial token and pass it to
|
|
`OAuth2TokenAgent.TokenAgent.start_link/1` along with the inline
|
|
`OAuth2TokenAgent.TokenRefreshStrategy` and the name for the `Agent`.
|
|
|
|
The client can be configured as described at
|
|
https://github.com/ueberauth/oauth2#configure-a-http-client.
|
|
|
|
```Elixir
|
|
client = Client.new([
|
|
strategy: OAuth2.Strategy.ClientCredentials,
|
|
client_id: "example_client_id",
|
|
client_secret: "example_client_secret",
|
|
site: "https://example.com/"
|
|
])
|
|
|
|
{:ok, agent} = OAuth2TokenAgent.TokenAgent.start_link(
|
|
name: MyModule.TokenAgent,
|
|
initial_client: client,
|
|
inline_refresh_strategy: %OAuth2TokenAgent.TokenRefreshStrategy{seconds_before_expires: 30}
|
|
)
|
|
```
|
|
|
|
The current version of the client can be retrieved using
|
|
`OAuth2TokenAgent.TokenAgent.get_client/1` with the agent's PID or name.
|
|
This client will automatically use the access token as a Bearer token in
|
|
the Authorization header when calling its request methods. The access token
|
|
itself can be retrieved from the client struct if it is desirable to use separately
|
|
configured clients and `OAuth2TokenAgent.TokenAgent.get_access_token/1` is
|
|
provided for convenience when doing so.
|
|
|
|
```Elixir
|
|
current_client = OAuth2TokenAgent.TokenAgent.get_client(agent)
|
|
current_client = OAuth2TokenAgent.TokenAgent.get_client(MyModule.TokenAgent)
|
|
access_token = OAuth2TokenAgent.TokenAgent.get_access_token(MyModule.TokenAgent)
|
|
```
|
|
|
|
The token can be refreshed by calling `OAuth2TokenAgent.TokenAgent.refresh_tokens/1`.
|
|
If a refresh token is available, it will be exchanged for a new set of tokens.
|
|
If no refresh token is available or the attempt to use the refresh results in an error,
|
|
the original client will be used again to attempt to obtain a new set of tokens.
|
|
|
|
```Elixir
|
|
:ok = OAuth2TokenAgent.TokenAgent.refresh(MyModule.TokenAgent)
|
|
```
|
|
|
|
This can be used to handle complex token refresh logic, but it will generally be
|
|
preferable to configure [a strategy](#inline-token-refresh-strategies).
|
|
|
|
### Inline Token Refresh Strategies
|
|
|
|
Inline token refresh strategies are used to determine when to obtain a new token
|
|
before returning the current client state or token value. The supported conditions
|
|
are show in the below example.
|
|
|
|
```Elixir
|
|
%TokenRefreshStrategy{
|
|
seconds_before_expires: 30, # refresh the token if it will expire in the next N seconds
|
|
every_seconds: 300 # refresh the token if it has been at least N seconds since the last refresh
|
|
}
|
|
```
|
|
|
|
If the Authorization Server does not provide an expiration time for the token, the
|
|
expiration time conditions will not trigger a refresh, so `:every_seconds` should
|
|
be used.
|
|
|
|
The inline refreshes only occur as part of request for data from the agent; this
|
|
saves unnecessary renewal requests in low-volume systems but tokens may be allowed
|
|
to expire if unused. If refresh tokens need to be kept active in a system where the
|
|
time between requests exceeds the token duration and the initial client cannot be
|
|
reused, using `OAuth2TokenAgent.TokenAgent.refresh` may be necessary.
|
|
|
|
The inline refreshes occur during message processing in the agent, which is not
|
|
concurrent per agent. This guarantees that redundant refresh calls will not be
|
|
made, which is particularly important when using single-use refresh tokens, but
|
|
also adds the latency of the refresh to the processing of the message that
|
|
triggers it.
|
|
|
|
The same strategy can have properties configured for multiple conditions and the
|
|
token will be refreshed if any of them are met.
|
|
|
|
If no value is specified when starting the agent, the behavior defaults to
|
|
`%TokenRefreshStrategy{seconds_before_expires: 30, every_seconds: 300}`.
|
|
|
|
### Configuring a Singleton
|
|
|
|
Providing a name for the agent allows it to be referred to in code without passing
|
|
around the PID, which is useful for reusing the same token whenever providing
|
|
credentials for the same principal, cutting down on the number of calls that need
|
|
to be made to the Authorization Server. A named instance can be added to a
|
|
supervision tree's children using a tuple like the below example. This will
|
|
launch the agent under the supervision tree and restart it if it crashes.
|
|
|
|
```Elixir
|
|
children = [
|
|
{TokenAgent, name: MyModule.TokenAgent, initial_client: client}
|
|
]
|
|
|
|
```
|