de Nirmalya Ghosh

Cum să vă autentificați API-urile Elixir / Phoenix folosind Guardian

Cum sa va autentificati API urile Elixir Phoenix folosind Guardian

Autentificarea este întotdeauna un subiect dificil. Oamenii tind să folosească atât de multe tipuri de autentificare în aplicațiile lor. Autentificarea utilizând o adresă de e-mail și o parolă cu o opțiune confirmă câmpul parolei este cea mai frecventă. Dar de fiecare dată când vedeți un formular de înregistrare, vă simțiți plictisit gândindu-vă că va trebui să tastați atât de mult doar pentru a vă înregistra! Care este distracția în asta!

Deci, pentru această aplicație, voi face autentificare utilizând contul dvs. Google. Este destul de simplu. Trebuie doar să faceți clic pe un buton, să acordați aplicației permisiunile necesare pentru a vă accesa profilul Google de bază și sunteți gata! Mișto, nu-i așa?

Vom folosi Ueberauth, Ueberauth Google și paznic pentru autentificarea utilizatorului nostru. Ueberauth și Ueberauth Google vor ajuta la autentificarea utilizatorului cu acreditările sale Google. Guardian ne va ajuta în generarea unui JSON Web Token pentru utilizatorii conectați. Acest simbol este necesar și trebuie să fie în antetul fiecărei cereri pentru orice rută care necesită autentificare.

Guardian va verifica acel jeton în antetul cererilor și, dacă jetonul este valid, rutele autentificate vor fi disponibile utilizatorului. Voi explica aceste lucruri în detalii.

Dacă nu ați fi instalat deja Phoenix și dependențele sale necesare, puteți merge la Phoenix Guides pentru a instala și a porni în funcțiune.

Pentru a începe, adăugați dependențele noastre mix.exs.

defp deps do  [   ...      {:ueberauth, "~> 0.4"},   {:ueberauth_google, "~> 0.2"},   {:ja_serializer, "~> 0.11.2"},   {:guardian, "~> 0.14.2"}]end

După aceasta, fugiți mix deps.get pentru a prelua dependențele.

De asemenea, trebuie să adăugați ueberauth și ueberauth_google la aplicația noastră din mix.exs.

def application do  [mod: {SocialAppApi, []},   applications: [   ...      :ueberauth, :ueberauth_google]]end

Acum, va trebui să adăugați ueberauth, ueberauth_google și guardian configurație la config/config.exs fişier.

# Ueberauth Config for oauthconfig :ueberauth, Ueberauth,  base_path: "/api/v1/auth",  providers: [    google: { Ueberauth.Strategy.Google, [] },    identity: { Ueberauth.Strategy.Identity, [        callback_methods: ["POST"],        uid_field: :username,        nickname_field: :username,      ] },  ]
# Ueberauth Strategy Config for Google oauthconfig :ueberauth, Ueberauth.Strategy.Google.OAuth,  client_id: System.get_env("GOOGLE_CLIENT_ID"),  client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),  redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
# Guardian configurationconfig :guardian, Guardian,  allowed_algos: ["HS512"], # optional  verify_module: Guardian.JWT,  # optional  issuer: "SocialAppApi",  ttl: { 30, :days },  allowed_drift: 2000,  verify_issuer: true, # optional  secret_key: System.get_env("GUARDIAN_SECRET") || "rFtDNsugNi8jNJLOfvcN4jVyS/V7Sh+9pBtc/J30W8h4MYTcbiLYf/8CEVfdgU6/",  serializer: SocialAppApi.GuardianSerializer

După cum puteți vedea aici, am folosit System.get_env() . Acesta este un mod de a stoca acreditările în aplicația dvs. pe care nu doriți să le faceți parte din baza de coduri. Puteți crea un .env înregistrați și stocați toate aceste acreditări, cum ar fi:

export DB_NAME_PROD="social_app_api_db"export DB_PASSWORD_PROD="password"export DB_USERNAME_PROD="password"

După aceasta, trebuie să faceți source .env și apoi, le puteți folosi în aplicația dvs.

Acum, va trebui să facem o grămadă de lucruri cu controlerele noastre, care vor permite utilizatorului să se înscrie sau să se conecteze.

Mai întâi, creați un fișier nou web/controllers/auth_controller.ex.

defmodule SocialAppApi.AuthController do  use SocialAppApi.Web, :controller  plug Ueberauth
  alias SocialAppApi.User  alias MyApp.UserQuery
  plug :scrub_params, "user" when action in [:sign_in_user]
  def request(_params) do  end
  def delete(conn, _params) do    # Sign out the user    conn    |> put_status(200)    |> Guardian.Plug.sign_out(conn)  end
  def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do    # This callback is called when the user denies the app to get the data from the oauth provider    conn    |> put_status(401)    |> render(SocialAppApi.ErrorView, "401.json-api")  end
  def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do    case AuthUser.basic_info(auth) do      {:ok, user} ->        sign_in_user(conn, %{"user" => user})    end
  case AuthUser.basic_info(auth) do      {:ok, user} ->        conn        |> render(SocialAppApi.UserView, "show.json-api", %{data: user})      {:error} ->        conn        |> put_status(401)        |> render(SocialAppApi.ErrorView, "401.json-api")    end  end
  def sign_in_user(conn, %{"user" => user}) do    try do      # Attempt to retrieve exactly one user from the DB, whose      # email matches the one provided with the login request      user = User      |> where(email: ^user.email)      |> Repo.one!
      cond do        true ->          # Successful login          # Encode a JWT          { :ok, jwt, _ } = Guardian.encode_and_sign(user, :token)
          auth_conn = Guardian.Plug.api_sign_in(conn, user)          jwt = Guardian.Plug.current_token(auth_conn)          {:ok, claims} = Guardian.Plug.claims(auth_conn)
          auth_conn          |> put_resp_header("authorization", "Bearer #{jwt}")          |> json(%{access_token: jwt}) # Return token to the client
        false ->          # Unsuccessful login          conn          |> put_status(401)          |> render(SocialAppApi.ErrorView, "401.json-api")      end    rescue      e ->        IO.inspect e # Print error to the console for debugging
        # Successful registration        sign_up_user(conn, %{"user" => user})    end  end
  def sign_up_user(conn, %{"user" => user}) do    changeset = User.changeset %User{}, %{email: user.email,      avatar: user.avatar,      first_name: user.first_name,      last_name: user.last_name,      auth_provider: "google"}
    case Repo.insert changeset do      {:ok, user} ->        # Encode a JWT        { :ok, jwt, _ } = Guardian.encode_and_sign(user, :token)
        conn        |> put_resp_header("authorization", "Bearer #{jwt}")        |> json(%{access_token: jwt}) # Return token to the client      {:error, changeset} ->        conn        |> put_status(422)        |> render(SocialAppApi.ErrorView, "422.json-api")    end  end
  def unauthenticated(conn, params) do    conn    |> put_status(401)    |> render(SocialAppApi.ErrorView, "401.json-api")  end
  def unauthorized(conn, params) do    conn    |> put_status(403)    |> render(SocialAppApi.ErrorView, "403.json-api")  end
  def already_authenticated(conn, params) do    conn    |> put_status(200)    |> render(SocialAppApi.ErrorView, "200.json-api")  end
  def no_resource(conn, params) do    conn    |> put_status(404)    |> render(SocialAppApi.ErrorView, "404.json-api")  endend

Aici sign_in_user va conecta utilizatorul și va arunca un access_token ca răspuns. sign_up_user va înregistra utilizatorul folosind acreditările Google și apoi va arunca un access_token ca răspuns. Acest simbol este esențial în modul în care Guardian va verifica acest lucru access_token în antetul tuturor cererilor. Acesta va verifica dacă utilizatorul este în prezent în sesiune sau nu. Dacă da, toate rutele autentificate vor fi disponibile utilizatorului. În caz contrar, va primi un 401 răspuns pentru rutele autentificate.

Să adăugăm câteva rute în aplicația noastră. Al nostru router.ex fișierul arată astfel:

defmodule SocialAppApi.Router do  use SocialAppApi.Web, :router
  pipeline :api do    plug :accepts, ["json", "json-api"]    plug JaSerializer.Deserializer  end
  pipeline :api_auth do    plug :accepts, ["json", "json-api"]    plug Guardian.Plug.VerifyHeader, realm: "Bearer"    plug Guardian.Plug.LoadResource    plug JaSerializer.Deserializer  end
  scope "/api/v1", SocialAppApi do    pipe_through :api_auth
  resources "/users", UserController, except: [:new, :edit]    get "/user/current", UserController, :current, as: :current_user    delete "/logout", AuthController, :delete  end
  scope "/api/v1/auth", SocialAppApi do    pipe_through :api
    get "/:provider", AuthController, :request    get "/:provider/callback", AuthController, :callback    post "/:provider/callback", AuthController, :callback  endend

Aici, conductă api_auth este cel care este autentificat. Conducta api nu este. Deci, putem vizita get “/:provider”, AuthController, :request fără să vă conectați.

Creați un alt fișier numit web/models/auth_user.ex cu următorul cod:

defmodule AuthUser do  alias Ueberauth.Auth
  def basic_info(%Auth{} = auth) do    {:ok,      %{        avatar: auth.info.image,        email: auth.info.email,        first_name: auth.info.first_name,        last_name: auth.info.last_name      }    }  endend

De asemenea, va trebui să creați un fișier User model.

mix phoenix.gen.json User users email:string auth_provider:string first_name:string last_name:string avatar:string

Acest lucru va genera modelul și migrarea necesară.

Modelul dvs. va arăta cam așa:

defmodule SocialAppApi.User do  use SocialAppApi.Web, :model
  schema "users" do    field :email, :string    field :auth_provider, :string    field :first_name, :string    field :last_name, :string    field :avatar, :string
    timestamps()  end
  def changeset(struct, params \ %{}) do    struct    |> cast(params, [:email, :auth_provider, :first_name, :last_name, :avatar])    |> validate_required([:email, :auth_provider, :first_name, :last_name, :avatar])    |> unique_constraint(:email)  endend

Fișierul dvs. de migrare va arăta cam așa:

defmodule SocialAppApi.Repo.Migrations.CreateUser do  use Ecto.Migration
  def change do    create table(:users) do      add :email, :string      add :auth_provider, :string      add :first_name, :string      add :last_name, :string      add :avatar, :string
      timestamps()    end
    # Unique email address constraint, via DB index    create index(:users, [:email], unique: true)  endend

Acum, executați migrarea.

mix ecto.migrate

De asemenea, creați un fișier UserController pentru noi user model. Acesta va conține următorul cod:

defmodule SocialAppApi.UserController do  use SocialAppApi.Web, :controller
  alias SocialAppApi.User
  plug Guardian.Plug.EnsureAuthenticated, handler:     SocialAppApi.AuthController
  def index(conn, _params) do    users = Repo.all(User)    render(conn, "index.json-api", data: users)  end
  def current(conn, _) do    user = conn    |> Guardian.Plug.current_resource
    conn    |> render(SocialAppApi.UserView, "show.json-api", data: user)  endend

Acest lucru este util în cazul în care doriți să verificați dacă rutele autentificate funcționează sau nu după toată munca grea.

Creați încă două vizualizări la web/views/error_view.ex cu următorul cod:

defmodule SocialAppApi.ErrorView do  use SocialAppApi.Web, :view  use JaSerializer.PhoenixView
  def render("401.json-api", _assigns) do    %{title: "Unauthorized", code: 401}    |> JaSerializer.ErrorSerializer.format  end
  def render("403.json-api", _assigns) do    %{title: "Forbidden", code: 403}    |> JaSerializer.ErrorSerializer.format  end
  def render("404.json-api", _assigns) do    %{title: "Page not found", code: 404}    |> JaSerializer.ErrorSerializer.format  end
  def render("422.json-api", _assigns) do    %{title: "Unprocessable entity", code: 422}    |> JaSerializer.ErrorSerializer.format  end
  def render("500.json-api", _assigns) do    %{title: "Internal Server Error", code: 500}    |> JaSerializer.ErrorSerializer.format  end
  # In case no render clause matches or no  # template is found, let's render it as 500  def template_not_found(_template, assigns) do    render "500.json-api", assigns  endend

De asemenea, creați o altă vizualizare web/views/user_view.ex cu următorul cod:

defmodule SocialAppApi.UserView do  use SocialAppApi.Web, :view  use JaSerializer.PhoenixView
  attributes [:avatar, :email, :first_name, :last_name, :auth_provider]end

Și, sunteți cu toții pregătiți. Porniți serverul:

mix phoenix.server

Acum, du-te la http: // localhost: 4000 / api / v1 / auth / google și veți fi redirecționat către pagina de autentificare Google. Odată ce acordați aplicației permisiunile necesare, veți primi un access_token în răspuns:

{  access_token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJVc2VyOjIiLCJleHAiOjE0ODk4NjM4MzUsImlhdCI6MTQ4NzI3MTgzNSwiaXNzIjoiU29jaWFsQXBwQXBpIiwianRpIjoiODU0NzJhODAtN2Q4Ny00MjM0LWIxNmUtODgyMTBmYWZkZDJmIiwicGVtIjp7fSwic3ViIjoiVXNlcjoyIiwidHlwIjoiYWNjZXNzIn0.L2LjpsyJAjF1r99hR11WVGcQ"}

Acum, puteți instala fișierul Modheader extensie pentru Chrome și orice altă extensie prin care puteți seta anteturi de răspuns. Adăuga Authorization ca Request Header si access_token cu Bearer <access_token>.

Cum sa va autentificati API urile Elixir Phoenix folosind Guardian
Modheader

Mergi la http: // localhost: 4000 / api / v1 / users și veți putea vedea o serie de utilizatori la care v-ați înscris deja. Puteți merge și la http: // localhost: 4000 / api / v1 / user / current pentru a vedea utilizatorul curent în sesiune.

Dacă eliminați acea valoare din Modheader și accesați http: // localhost: 4000 / api / v1 / users, veți primi următorul răspuns:

{  jsonapi: {    version: "1.0"  },  errors: [{    title: "Unauthorized",    code: 401  }]}

După cum am menționat mai devreme, trebuie să trimiteți access_token primit pentru a vizualiza rutele autentificate. Acum, știi cum să faci autentificarea API în Elixir. Puteți compara codul cu codul meu de pe Github.

Dacă aveți ceva feedback, anunțați-mă în comentariile de mai jos.