-
-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API integration guide #176
Comments
That would be great to have ! 😄 |
Here's an example of an API auth plug with renewal logic commented out: defmodule MyAppWeb.Pow.Plug do
use Pow.Plug.Base
alias Plug.Conn
alias Pow.Config
@impl true
@spec fetch(Conn.t(), Config.t()) :: {Conn.t(), map() | nil}
def fetch(conn, config) do
token = fetch_auth_token(conn)
config
|> store_config()
|> Pow.Store.CredentialsCache.get(token)
|> case do
:not_found -> {conn, nil}
user -> {conn, user}
end
end
@impl true
@spec create(Conn.t(), map(), Config.t()) :: {Conn.t(), map()}
def create(conn, user, config) do
token = Pow.UUID.generate()
# renew_token = Pow.UUID.generate()
conn = Conn.put_private(conn, :pow_auth_token, token)
# conn = Conn.put_private(conn, :pow_auth_renew_token, renew_token)
config
|> store_config()
|> Pow.Store.CredentialsCache.put(token, user)
# config
# |> store_config()
# |> PowPersistentSession.Store.PersistentSessionCache.put(renew_token, user.id)
{conn, user}
end
@impl true
@spec delete(Conn.t(), Config.t()) :: Conn.t()
def delete(conn, config) do
token = fetch_auth_token(conn)
config
|> store_config()
|> Pow.Store.CredentialsCache.delete(token)
conn
end
# @doc """
# Create a new token with the provided authorization token.
#
# The renewal authorization token will be deleted from the store after the user id has been fetched.
# """
# @spec renew(Conn.t(), Config.t()) :: {Conn.t(), map() | nil}
# def renew(conn, config) do
# renew_token = fetch_auth_token(conn)
# store_config = store_config(config)
# user_id = PowPersistentSession.Store.PersistentSessionCache.get(store_config, renew_token)
# PowPersistentSession.Store.PersistentSessionCache.delete(store_config, renew_token)
#
# load_and_create_session(user_id, conn, config)
# end
# defp load_and_create_session(:not_found, conn, _config), do: {conn, nil}
# defp load_and_create_session(id, conn, config) do
# case load_user(id) do
# nil -> {conn, nil}
# user -> create(conn, user, config)
# end
# end
defp fetch_auth_token(conn) do
conn
|> Plug.Conn.get_req_header("authorization")
|> List.first()
end
defp store_config(config) do
backend = Config.get(config, :cache_store_backend, Pow.Store.Backend.EtsCache)
[backend: backend]
end
end So I imagine that a dev just sets up the API routes: pipeline :api do
plug MyAppWeb.Pow.Plug, otp_app: :my_app
end
pipeline :browser do
plug Pow.Plug.Session, otp_app: :my_app
end And then deals with the the renewal method directly in the controllers: defmodule MyAppWeb.API.AuthController do
use MyAppWeb, :controller
alias MyAppWeb.Pow.Plug, as: AuthPlug
def create(conn, %{"user" => user_params}) do
conn
|> Pow.Plug.authenticate_user(user_params)
|> case do
{:ok, conn} -> # display success response and pass auth token
{:error, conn} -> # display error response
end
end
def delete(conn, _params) do
{:ok, conn} = Pow.Plug.clear_authenticated_user(conn)
# display success response
end
def renew(conn, _params) do
config = Pow.Plug.fetch_config(conn)
case AuthPlug.renew(conn, config) do
{conn, nil} -> # display error response
{conn, _user} -> # display success response and pass auth token
end
end
end |
Hi, I will not create a new issue because I only have questions, but I guess the I want to expose a route with API key auth, but I do not need any session, just accept the request, check the API key header and proceed. I am not sure what I should implement in the |
If you don't need to store the API key anywhere then you can take a look at the example in the readme using You just have to replace all the Plug session stuff there. You are right that the docs for |
In my case it is the opposite, I have API keys stored in database, so I am implementing the |
All I did for now is the following: def create(_, _, _) do
raise "called create"
end
def delete(_, _) do
raise "called delete"
end
def fetch(conn, _config) do
case fetch_auth_token(conn) do
nil ->
{conn, nil}
token ->
IO.inspect(token, label: "token")
case Revamp.Repo.get_by(ApiKey, keyval: token) do
nil ->
{conn, nil}
%ApiKey{} = api_key ->
user = Ecto.assoc(api_key, :user)
conn = Conn.put_private(conn, :revamp_api_key_is_write, api_key.is_write)
{conn, user}
end
end
end
defp fetch_auth_token(conn) do
conn
|> Plug.Conn.get_req_header("authorization")
|> List.first()
end I have two kind of keys, the "read" keys that will be visible in the html/javascript of client applications, stored in plaintext, and the "write" keys that allows the request to change data. Should I implement hashing with bcrypt on my own for these ? |
I've updated your example with some more logic to give you a better idea how it should work: defmodule MyAppWeb.Pow.Plug do
use Pow.Plug.Base
alias Plug.Conn
alias MyApp.ApiKeys
@impl true
def fetch(conn, _config) do
conn
|> fetch_auth_token()
|> load_key()
|> case do
nil ->
{conn, nil}
key ->
conn = Conn.put_private(conn, :revamp_api_key_is_write, key.is_write)
{conn, key.user}
end
end
defp fetch_auth_token(conn) do
conn
|> Plug.Conn.get_req_header("authorization")
|> List.first()
end
defp load_key(nil), do: nil
defp load_key(token), do: ApiKeys.get_by_keyval(token)
@impl true
def create(conn, user, _config) do
conn =
case ApiKeys.create(user) do
{:ok, key} -> Conn.put_private(conn, :api_key, key)
{:error, _changeset} -> conn
end
{conn, user}
end
@impl true
def delete(conn, _config) do
conn
|> fetch_auth_token()
|> load_key()
|> invalidate_key()
conn
end
defp invalidate_key(nil), do: nil
defp invalidate_key(key), do: ApiKeys.invalidate(key)
end
defmodule MyApp.KeyApis do
alias MyApp.{ApiKeys.ApiKey, Repo}
import Ecto.Query
def get_by_keyval(token) do
query = from k in ApiKey, where: is_nil(k.invalidated_at) and k.token == ^token
Repo.one(query)
end
def invalidate(key) do
key
|> ApiKey.changeset_delete()
|> Repo.update()
end
def create(user) do
key
|> ApiKey.changeset(%{user: user})
|> Repo.insert()
end
end |
So I understand in my case that Using the plug may be unessecary but it keeps my code clean and declarative, relying on pow to call the error handler if needed or to put the user in session, I just have to write a function to read the key and maybe return a user. Thank you for your time. |
Yup, you don't need create and delete in this case, and omitting them or raising an error is what you should do. It won't be triggered unless you use the plug to sign in users with their credentials, or log out. |
Thanks ! |
does anyone have an example of the PowInvitation extension with an api? |
@eamonpenland I don't know of any example, but there's plans for a full API guide (though only for |
Ahh awesome... actually got basic implementation working...let me know if I
should pr
…On Sat, Mar 28, 2020, 8:11 PM Dan Schultzer ***@***.***> wrote:
@eamonpenland <https://github.com/eamonpenland> I don't know of any
example, but there's plans for a full API guide (though only for
PowEmailConfirmation and PowResetPassword currently): pow-auth/pow_site#14
<pow-auth/pow_site#14>
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#176 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAZCXLUSGE6HR6UUII7ODMTRJ2G2VANCNFSM4HJT55ZQ>
.
|
Sure! Please push it to the pow_site repo, as it's out of scope for the Pow docs. A comment/gist/repo is also fine 😄 (as seen in pow-auth/pow_site#14 with a repo containing the guides for the other two extension) I would like to put all of them together and turn it into a complete A-Z guide for API integration. |
There's been a few issues with questions on how to integrate Pow with an API built in Phoenix: #170 #167 #153
A guide on API integration should be written. While I believe many would use absinthe, I think the guide should just focus on using plain Phoenix.
There may be some changes that can be made to Pow to make it as easy as Pow currently is for browser pipeline setup for API integration. However, I have a feeling that it's better to be explicit with routes for API so requiring the developer to add the individual routes rather than having a
pow_routes/0
orpow_api_routes/0
macro. Sessions can be used as API keys, but it may be better to show how to usePhoenix.Token
instead.The text was updated successfully, but these errors were encountered: