Phoenix와 Expo가 제작한 스마트폰 애플리케이션 ②JWT인증+CRUD편

Phoenix와 Expo로 제작된 스마트폰 애플리케이션① Phoenix 설정편+phxgen_auth
Phoenix와 Expo가 제작한 스마트폰 애플리케이션 ②JWT인증+CRUD편<-본문
저번elixir,phoenix 설정 및 프로젝트 만들기
phx-gen-auth를 사용하여 인증 기능을 가진 사용자를 만들었습니다
이번에는 Guardian을 이용해서 프론트 데스크와의 JWT 인증과 CRUD 기능을 실현하려고 합니다.

크루드 제작


이전 기사에서 User만 생성되었을 뿐 API 주위에 필요한 파일이 없기 때문에 우선 다음 명령을 사용하여 API 주위의 파일과 함께 CRUD를 생성합니다
mix phx.gen.json Posts Post posts body:string user_id:references:users
일반적인 응용 상황에서phx.은gen.}이지만 API의 경우 phx입니다.gen.json 사용
사용자와 관련이 있기 때문에userusers로 외부 키와 관련자 설정
다음 관계 설정
[new] lib/sns/posts/post.ex
defmodule Sns.Posts.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :body, :string
    field :user_id, :id # <- こっちは消す
    
    belongs_to :user, Sns.Users.User # <-こっちを書き加える
    timestamps()
  end

  @doc false
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:body, :user_id]) # <- user_idを追加
    |> validate_required([:body, :user_id]) # <- user_idを追加
  end
end
[edit] lib/sns/users/user.ex
defmodule Sns.Users.User do
  use Ecto.Schema
  import Ecto.Changeset

  @derive {Inspect, except: [:password]}
  schema "users" do
    field :email, :string
    field :password, :string, virtual: true
    field :hashed_password, :string
    field :confirmed_at, :naive_datetime

    has_many :posts, Sns.Posts.Post # <- これを追加
    timestamps()
  end
...
end

경비를 설치하다.


Guardian 정보여기.
mix.exs에 다음mixdeps를 추가합니다.실행 get
[edit]mix.exs
  defp deps do
    [
      ...
      {:guardian, "~> 2.0"}
    ]
  end

다음 참조여기. 설정 Guardian
secret_키는 mix guardian입니다.gen.secret에서 만든 파일을 붙여넣거나 환경 변수를 설정하십시오
[edit]config/config.exs
config :sns, Sns.Guardian,
  issuer: "sns",
  secret_key: "Secret key. You can use `mix guardian.gen.secret` to get one"

[new]lib/sns/guardian.ex
defmodule Sns.Guardian do
  use Guardian, otp_app: :sns

  # Guardian.encode_and_sign(sign_up/sign_in)で実行
  def subject_for_token(user, _claims) do
    sub = to_string(user.id)
    {:ok, sub}
  end

  # headerのBearerのJWTを検証時(sign_up/sign_in以外のAPI)に実行
  def resource_from_claims(claims) do
    id = claims["sub"]
    resource = Sns.Users.get_user!(id)
    {:ok, resource}
  end
end
README.md에subjectfor_token, resource_from_claims에도 오류가 있을 때 함수가 있지만,comple에서는 다음과 같은 경고가 나타나고, 오류가 있을 때 뒤에 설명된guardian의 error가 나타납니다.handler 및 fallbackcontroller가 처리하기 때문에 평상시 함수만 있습니다
warning: this clause for resource_from_claims/1 cannot match because a previous clause at line 10 always matches

인증 기능의 구현(Model)


Guardian 설정이 완료되었으므로 인증 섹션 만들기
저번phx-gen-auth에서 웹 화면의 인증 부분을 만들었기 때문에 JWT도 이 부분을 사용하여 실현할 것이다
[edit]lib/sns/users.ex
defmodule Sns.Users do
  ...
  alias Sns.Guardian

  @doc """
  Generates a JWT
  """
  def token_sign_in(email, password) do
    if user = get_user_by_email_and_password(email, password) do
      Guardian.encode_and_sign(user)
    else
      {:error, :unauthorized}
    end
  end
  ...
end

인증 기능의 구현(Controller, View)


다음은 디렉터 및 뷰 생성
API와 같은 일반적인/appi/v1 폴더 구성은 controllers/appi/v1과 폴더를 만들고 생성된 파일의 모듈 이름을 Sns로 지정합니다.Api.V1.User Controller라면 괜찮습니다.
[new]lib/sns_web/api/v1/user_controller.ex
defmodule SnsWeb.Api.V1.UserController do
  use SnsWeb, :controller

  alias Sns.Users
  alias Sns.Users.User
  alias Sns.Guardian

  action_fallback SnsWeb.FallbackController

  def show(conn, _params) do
    user = Guardian.Plug.current_resource(conn)
    render(conn, "show.json", user: user)
  end

  def create(conn, %{"user" => user_params}) do
    with {:ok, %User{} = user} <- Users.register_user(user_params) do
      {:ok, token, _claims} = Guardian.encode_and_sign(user)
      conn |> render("jwt.json", token: token)
    end
  end

  def sign_in(conn, %{"email" => email, "password" => password}) do
    with {:ok, token, _claims} <- Users.token_sign_in(email, password) do
      conn |> render("jwt.json", token: token)
    end
  end
end

action_fallback 설정의 Fallback Controller
controller에서 오류가 발생했을 때 대응하는 오류를 되돌려줍니다
이렇게 되면 하나하나 잘못 처리할 필요가 없다
그리고 이거랑 changeeset.view는 px입니다.아까 젠슨이 모형을 만들 때 같이 만들었어요.
뷰는 로그인과 새로 만들 때 돌아오는 JWT와 테스트용 쇼만 있기 때문에 필요에 따라 제작하세요
[new]lib/sns_web/views/api/v1/user_view.ex
defmodule SnsWeb.Api.V1.UserView do
  use SnsWeb, :view

  def render("show.json", %{user: user}) do
    %{data: %{id: user.id, email:  user.email}}
  end

  def render("jwt.json", %{token: token}) do
    %{token: token}
  end
end

인증 기능의 구현(Router)


마지막으로 러터 파트.
인증이 필요한 곳은 jwt앱을 통해
[edit]lib/sns_web/router.ex
Guardian의 설정으로 제작된 Guardian입니다.ex와guardian 오류를 처리하는 모듈을 설정합니다
[new]lib/sns_web/api_auth_pipeline.ex
defmodule SnsWeb.Router do
  alias SnsWeb.ApiAuthPipeline

  pipeline :jwt_authenticated do
    plug ApiAuthPipeline
  end

  scope "/api/v1", SnsWeb do
    pipe_through :api

    post "/sign_up", Api.V1.UserController, :create
    post "/sign_in", Api.V1.UserController, :sign_in
  end

  scope "/api/v1", SnsWeb do
    pipe_through [:api, :jwt_authenticated]

    get "/mypage", Api.V1.UserController, :show
    resources "/posts", Api.V1.PostController, except: [:new, :edit]    
  end
end
설정 작업
[new]lib/sns_web/api_auth_error_handler.ex
defmodule SnsWeb.ApiAuthPipeline do
  use Guardian.Plug.Pipeline, otp_app: :sns,
    module: Sns.Guardian,
    error_handler: SnsWeb.ApiAuthErrorHandler

  plug Guardian.Plug.VerifyHeader, realm: "Bearer"
  plug Guardian.Plug.EnsureAuthenticated
  plug Guardian.Plug.LoadResource
end

사용자 signup/signin 동작 확인


설치가 끝났습니다. 작업을 확인하십시오.
동작 확인 사용Postman
새로 만들기 실패 시
px-auth-gen의 초기 설정에서 비밀번호가 12글자를 초과하여 오류가 발생했습니다

새로 만들기 성공 시
성공, 영패 이미 반환

로그인 실패 시

로그인 성공 시
성공, 영패 이미 반환

토큰 인증은 Authorization 태그를 선택하고 type을 Bearer Token으로 설정하십시오.
jwt 인증 실패 시

jwt 인증 성공 시
Authorization의 Token에 로그인 성공 또는 새로 작성된 Token을 설정하십시오.

정상적인 응답과 오류를 확인했을 때의 응답이 모두 왔습니다
#CRUD 작업 확인
그 다음에 크루드도 동작 확인을 하고 그 전에 손발을 살짝 움직일 거예요.
Post 작성 시 user 사용id가 필요하지만 그것을 얻을 때
defmodule SnsWeb.ApiAuthErrorHandler do
  import Plug.Conn

  def auth_error(conn, {type, _reason}, _opts) do
    body = Jason.encode!(%{error: to_string(type)})
    send_resp(conn, 401, body)
  end
end
귀찮아서 피플라인으로 처리id에서 얻기 위해서.
[new] lib/sns_web/auth_helper.ex
alias Sns.Guardian

def create(conn, %{"post" => params})
  post_params = Map.put(
      params,
      :user_id, 
      Guardian.Plug.current_resource(conn).id
  )
  ....
end
[edit] lib/sns_web/router.ex
defmodule SnsWeb.AuthHelper do
  import Guardian.Plug

  def init(opts), do: opts

  def call(conn, _opts) do
    Map.put(conn, :user_id, current_resource(conn).id)
  end
end
[edit] lib/sns_web/controllers/api/v1/post_controller.ex
defmodule SnsWeb.Router do
  use SnsWeb, :router
  alias SnsWeb.ApiAuthPipeline
  alias SnsWeb.AuthHelper # <- これを追加
  import SnsWeb.UserAuth

  pipeline :jwt_authenticated do
    plug ApiAuthPipeline
    plug AuthHelper # <- これを追加
  end
이렇게 하면 좀 가벼워요.
이렇게 전체를 통해서 중간에 처리하고 싶을 때 pipeline에 넣으면 매끄러워요.
그럼 동작 확인을 해보도록 하겠습니다.
글을 쓰다

기사 목록

기사 편집

기사 삭제
204 no_콘텐츠가 돌아왔습니다.

CRUD 작동 확인
JWT 인증 + CRUD 편 이상 감사합니다.
다음에는 Post에 이미지를 첨부하여 파일 업로드 섹션을 작성합니다.

낙하점


Repo에서 user의 기록을 얻었지만 Guardian의 인증이 순조롭지 않을 때
IO.inspect(Guardian.encode_and_sign(user))
실행 중 다음 오류 발생
{:error, :secret_not_found}
원인은 config입니다.exs의 sercret키의 타자입니다.
otp_app, issuper, app 이름도 타자 실수하기 쉬운 부분이기 때문에 Guardian만 잘 안 될 때 타자 방식을 의심하세요.

이번 일


https://github.com/thehaigo/sns/commit/1fe9475ba0c57c0280c3d4446b63e2a2fe9ff6e7

참조 페이지

  • https://medium.com/@njwest/jwt-auth-with-an-elixir-on-phoenix-1-3-guardian-api-and-react-native-mobile-app-1bd00559ea51
  • https://medium.com/@zeke8402/your-first-versioned-api-with-phoenix-framework-da0bc7897db6
  • https://github.com/ueberauth/guardian#installation
  • https://elixirschool.com/ja/lessons/libraries/guardian/
  • https://www.tech-note.info/entry/phoenix-7-controllers-3
  • https://qiita.com/shufo/items/fadf75b13d9bef408ab0
  • https://github.com/ueberauth/guardian/issues/523
  • 좋은 웹페이지 즐겨찾기