Проблемы с опекуном - как сгенерировать доступ и обновить токен при входе в систему - PullRequest
0 голосов
/ 03 мая 2018

Чтобы сделать недействительными JWT, люди используют один из двух методов

  • черный / белый список (с guardian_db).
  • токен обновления (который позволяет регенерировать токены доступа) с коротким токеном с истекающим сроком действия.

Я не хочу использовать guardian_db в моем проекте. Итак, как мне сгенерировать токен доступа и обновить токен в конечной точке входа в систему?

Мой код:

Файл mix.ex

# Authentication library
{:guardian, "~> 1.0"}

Файл config.exs

# Guardian configuration
config :my_proj, MyProj.Guardian,
issuer: "my_proj",
verify_module: Guardian.JWT,
secret_key: "Xxxxxxxxxxxxxxxxxxxxxxxxxx",
allowed_drift: 2000,
verify_issuer: true,
ttl: {5, :minutes}

# Ueberauth configuration
config :ueberauth, Ueberauth,
base_path: "/api/auth",
providers: [
identity:
{Ueberauth.Strategy.Identity,
[
callback_methods: ["POST"],
callback_path: "/api/auth/login",
nickname_field: :email,
param_nesting: "account",
uid_field: :email
]}
]

Файл aut_access_pipeline.ex

defmodule MyProjWeb.Plug.AuthAccessPipeline do
use Guardian.Plug.Pipeline, otp_app: :my_proj

plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})
plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
plug(Guardian.Plug.EnsureAuthenticated)
plug(Guardian.Plug.LoadResource, ensure: true)
plug(MyProjWeb.Plug.CurrentUser)
plug(MyProjWeb.Plug.CheckToken)
end

Файл auth_controller.ex

defmodule MyProjWeb.AuthenticationController do
def login(conn, %{"email" => email, "password" => password}) do
case Accounts.get_user_by_email_and_password(email, password) do
{:ok, user} ->
handle_login_response(conn, user, "login.json")
end
{:error, _reason} ->
conn
|> put_status(:unauthorized)
|> Error.render(:invalid_credentials)
|> halt
end
end

def refresh(conn, %{"jwt" => existing_jwt}) do
case MyProj.Guardian.refresh(existing_jwt, ttl: {5, :minutes}) do
{:ok, {_old_token, _old_claims}, {new_jwt, _new_claims}} ->
current_user = current_resource(conn)
user = MyProj.Accounts.get_user!(current_user.id)

conn
|> put_resp_header("authorization", "Bearer #{new_jwt}")
|> render(MyProjWeb.AuthenticationView, json_file, %{jwt: new_jwt, user: user})
{:error, _reason} ->
conn
|> put_status(:unauthorized)
|> Error.render(:invalid_credentials)
end
end

defp handle_login_response(conn, user, json_file) do
new_conn = MyProj.Guardian.Plug.sign_in(conn, user, [])
jwt = MyProj.Guardian.Plug.current_token(new_conn)
%{"exp" => exp} = MyProj.Guardian.Plug.current_claims(new_conn)

new_conn
|> put_resp_header("authorization", "Bearer #{jwt}")
|> put_resp_header("x-expires", "#{exp}")
|> render(MyProjWeb.AuthenticationView, json_file, %{jwt: jwt, user: user})
end
end

Файл guardian.ex

defmodule MyProj.Guardian do
use Guardian, otp_app: :my_proj

def subject_for_token(user = %User{}, _claims),
do: {:ok, "User:#{user.id}"}

def subject_for_token(_user, _claims) do
{:error, :no_resource_id}
end

def resource_from_claims(%{"sub" => "User:" <> id}) do
{:ok, MyProj.Accounts.get_user!(id)}
end

def resource_from_claims(_claims) do
{:error, :no_claims_sub}
end
end

С этим кодом у меня есть действительный токен для доступа, но я не знаю, как создать токен обновления для регенерации / обновления токена доступа, когда он истекает ...

кто-нибудь может помочь?

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Мое решение примерно такое ...

В конечной точке входа я отвечаю 2 токенами (доступ и обновление) ...

new_conn =
  MyProj.Guardian.Plug.sign_in(
    conn,
    credentials,
    %{},
    token_type: "refresh"
  )

jwt_refresh = MyProj.Guardian.Plug.current_token(new_conn)

{:ok, _old_stuff, {jwt, %{"exp" => exp} = _new_claims}} =
  MyProj.Guardian.exchange(jwt_refresh, "refresh", "access")

В основном решение заключается в использовании MyProj.Guardian.exchange (...)

В конечной точке обновления я отвечаю новым маркером доступа ...

def refresh(conn, %{"jwt_refresh" => jwt_refresh}) do
case MyProj.Guardian.exchange(jwt_refresh, "refresh", "access") do
  {:ok, _old_stuff, {jwt, %{"exp" => exp} = _new_claims}} ->
    conn
    |> put_resp_header("authorization", "Bearer #{jwt}")
    |> put_resp_header("x-expires", "#{exp}")
    |> render(MyProjWeb.AuthenticationView, "refresh.json", %{jwt: jwt})

  {:error, _reason} ->
    conn
    |> put_status(:unauthorized)
    |> Error.render(:invalid_credentials)
end
end
0 голосов
/ 03 мая 2018

Я использую старую версию Guardian, поэтому мой код немного отличается, но если вы хотите сделать это в той же конечной точке, вы можете сопоставить параметр grant_type в теле

def login(conn, %{"grant_type" => "password"} = params) do
 case User.find_and_confirm_password(params) do
  {:ok, user} ->
    new_conn = GP.api_sign_in(conn, user, :token, perms: User.permissions(user))
    jwt = GP.current_token(new_conn)
    {:ok, claims} = GP.claims(new_conn)
    exp = Map.get(claims, "exp") -
      (DateTime.utc_now() |> DateTime.to_unix())

    new_conn
    |> put_resp_header("authorization", "Bearer #{jwt}")
    |> put_resp_header("x-expires", "Bearer #{exp}")
    |> render(SessionView, "login.json", jwt: jwt, exp: exp)
  {:error, changeset} ->
    conn
    |> put_status(401)
    |> render(IIMWeb.ChangesetView, "error.json", changeset: changeset)
end

конец

def login(conn, %{"grant_type" => "refresh_token"} = params) do
  jwt = GP.current_token(conn)
  with {:ok, claims} <- GP.claims(conn),
    logged_user_id when not is_nil(logged_user_id) <-
      GP.current_resource(conn)[:id],
    user <- Repo.get!(User, logged_user_id),
    {:ok, new_jwt, new_claims} <- Guardian.refresh!(jwt, claims,
      perms: User.permissions(user))
  do
    exp = Map.get(new_claims, "exp") - (DateTime.utc_now() |> DateTime.to_unix())
    conn
    |> put_resp_header("authorization", "Bearer #{new_jwt}")
    |> put_resp_header("x-expires", "Bearer #{exp}")
    |> render(SessionView, "login.json", jwt: new_jwt, exp: exp)
  else
    {:error, reason} ->
      changeset = %Login{}
      |> Login.changeset(params)
      |> Changeset.add_error(:refresh_token, to_string(reason))
      conn
      |> put_status(401)
      |> render(IIMWeb.ChangesetView, "error.json", changeset: changeset)
end

конец

Как вы можете видеть, я использую ту же конечную точку, но в зависимости от grant_type он получает пароль или старый токен (этот запрос должен быть выполнен за несколько секунд до истечения срока действия старого токена) Я изменил время экспозиции из будущей временной метки на время в миллисекундах из-за часовых поясов во внешнем интерфейсе, но вы можете использовать его как хотите.

...