View Source README
Boruta OAuth/OpenID Connect provider core
Boruta is the core of an OAuth/OpenID Connect provider giving authorization business rules. A generator is provided to create phoenix controllers, views and templates to have a basic provider up and running.
It is intended to follow RFCs:
- RFC 6749 - The OAuth 2.0 Authorization Framework
- RFC 7662 - OAuth 2.0 Token Introspection
- RFC 7009 - OAuth 2.0 Token Revocation
- RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients
And specification from OpenID Connect:
This package is meant to help to provide OAuth 2.0/OpenID Connect to your applications implementing part or all of authorization code, implicit, hybrid, client credentials, or resource owner password credentials grants. It also helps introspecting and revoking tokens.
Documentation
Master branch documentation can be found here
Stable documentation is hosted on hexdocs.pm
Live example
A live example can be found here
Integration example
An example of integration can be found here
Setup
- Schemas migration
If you plan to use Boruta builtin clients and tokens contexts, you'll need a migration for its Ecto
schemas. This can be done by running:
mix boruta.gen.migration
Note: You may need to run the task above in case of package upgrade to have up to date database schema.
- Implement ResourceOwners context (optional)
In order to have user flows operational, You need to implement Boruta.Oauth.ResourceOwners
behaviour.
Here is an example implementation:
defmodule MyApp.ResourceOwners do
@behaviour Boruta.Oauth.ResourceOwners
alias Boruta.Oauth.ResourceOwner
alias MyApp.Accounts.User
alias MyApp.Repo
@impl Boruta.Oauth.ResourceOwners
def get_by(username: username) do
with %User{id: id, email: email} <- Repo.get_by(User, email: username) do
{:ok, %ResourceOwner{sub: to_string(id), username: email}}
else
_ -> {:error, "User not found."}
end
end
def get_by(sub: sub) do
with %User{id: id, email: email} <- Repo.get_by(User, id: sub) do
{:ok, %ResourceOwner{sub: to_string(id), username: email}}
else
_ -> {:error, "User not found."}
end
end
@impl Boruta.Oauth.ResourceOwners
def check_password(resource_owner, password) do
user = Repo.get_by(User, id: resource_owner.sub)
case User.valid_password?(user, password) do
true -> :ok
false -> {:error, "Invalid email or password."}
end
end
@impl Boruta.Oauth.ResourceOwners
def authorized_scopes(%ResourceOwner{}), do: []
end
- Configuration
Boruta provides several configuration options that you can customize in config.exs
. Those have following default values:
config :boruta, Boruta.Oauth,
repo: MyApp.Repo, # mandatory
cache_backend: Boruta.Cache,
contexts: [
access_tokens: Boruta.Ecto.AccessTokens,
clients: Boruta.Ecto.Clients,
codes: Boruta.Ecto.Codes,
resource_owners: MyApp.ResourceOwners, # mandatory for user flows
scopes: Boruta.Ecto.Scopes
],
max_ttl: [
authorization_code: 60,
access_token: 60 * 60 * 24,
id_token: 60 * 60 * 24,
refresh_token: 60 * 60 * 24 * 30
],
token_generator: Boruta.TokenGenerator,
issuer: "boruta"
Integration
This implementation follows an inverted hexagonal architecture, dependencies are inverted from Application layer.
In order to expose endpoints of an OAuth/OpenID Connect server with Boruta, you need implement either the behaviour Boruta.Oauth.Application
or the behaviours Boruta.Oauth.AuthorizeApplication
, Boruta.Oauth.TokenApplication
, Boruta.Oauth.IntrospectApplication
and Boruta.Oauth.RevokeApplication
to integrate these endpoints separatly. Those behaviours will help you creating callback functions which will be triggered by invoking token/2
, authorize/2
, introspect/2
and revoke/2
functions from Boruta.Oauth
module.
A generator is provided to create phoenix controllers, views and templates needed to implement a basic OAuth/OpenID Connect server.
mix boruta.gen.controllers
This task will create needed files and give you a guide to finish your setup.
Straightforward testing
You can also create a client and test it
alias Boruta.Ecto
alias Boruta.Oauth.Authorization
alias Boruta.Oauth.{ClientCredentialsRequest, Token}
# create a client
{:ok, %Ecto.Client{id: client_id, secret: client_secret}} = Ecto.Admin.create_client(%{})
# obtain a token
{:ok, %Token{value: value}} = Authorization.token(%ClientCredentialsRequest{client_id: client_id, client_secret: client_secret})
# check token
{:ok, _token} = Authorization.AccessToken.authorize(value: value)
Guides
Here are some guides helping the integration of OAuth/OpenID Connect in your systems:
- Basic OAuth/OpenID Connect provider integration
- How to create an OAuth client
- Client request authorization
- Notes about pkce
Feedback
It is a work in progress, all feedbacks / feature requests / improvements are welcome