commit 5d4d4e8495d4873bf55fca688f001193a14c5aa9 Author: theMarinac Date: Wed Dec 11 14:48:47 2019 +0100 Readme add diff --git a/README.md b/README.md new file mode 100644 index 0000000..90155b2 --- /dev/null +++ b/README.md @@ -0,0 +1,217 @@ +# Auth tutorial for setting up basic user auth with `put_session()` + +**NOTE: PHOENIX_APP and PHOENIX_APP_WEB are just macros here, in your app user your apps name!** + +## Deps used + +```bash +# Admin auth +{:basic_auth, "~> 2.2.2"}, +# Password hashing +{:bcrypt_elixir, "~> 2.0"} +``` + +## Setup + +Start off by running `mix deps.get` + +### User Schema + +Make sure that you have some context that is like `Accounts.User` with `email:unique` and `password_hash` and that you have `Accounts` module with `get_user!(id)`... etc. + +You can do this with + +```bash +terminal:~/PHOENIX_PROJECT/$ mix phx.gen.html Accounts User users email:string:unique password_hash:string +``` + +### Password hashing + +After that open up `accounts/user.ex` and add line bellow to `changeset()` + +```elixir +def changeset(user, attrs) do + ... + |> update_change(:password_hash, &Bcrypt.hash_pwd_salt/1) +end +``` + +### Auth helper + +In `PHOENIX_APP_WEB/` create folder `/helpers` and create file `auth.ex` + +```elixir +defmodule PHOENIX_APP_WEB.Helpers.Auth do + import Plug.Conn, only: [get_session: 2] + alias PHOENIX_APP.{Repo, Accounts.User} + + def signed_in?(conn) do + user_id = get_session(conn, :current_user_id) + if user_id, do: !!Repo.get(User, user_id) + end +end +``` + +Then open your `PHOENIX_APP_WEB.ex` and inside add this `import`, we are doing this because we want to have this function in every `view` + +```elixir +def view do + quote do + ... + import PHOENIX_APP_WEB.Helpers.Auth, only: [signed_in?: 1] + end +end +``` + +### Session templates/view/router/controller + +#### Templates + +Create `/session` folder in `/templates` and inside create 2 files `sign-in.html.eex` and `sign-up.html.eex` +One will be used for Signing in and the other for Signing out + +###### /session/sign-in.html + +```elixir +<%= form_for @conn, Routes.session_path(@conn, :create_session),[as: :session] , fn f -> %> + + <%= label f, :email %> + <%= text_input f, :email %> + <%= error_tag f, :email %> + + <%= label f, :password %> + <%= password_input f, :password_hash %> + <%= error_tag f, :password_hash %> + +
+ <%= submit "Sign in" %> +
+<% end %> + +``` + +###### /session/sign-up.html + +```elixir +<%= form_for @changeset, Routes.session_path(@conn, :create_user) , fn f -> %> + <%= if @changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= label f, :email %> + <%= text_input f, :email %> + <%= error_tag f, :email %> + + <%= label f, :password %> + <%= password_input f, :password_hash %> + <%= error_tag f, :password_hash %> + +
+ <%= submit "Sign up" %> +
+<% end %> + +``` + +#### Views + +Create `session_view.ex` in `/views` folder + +```elixir +defmodule PHOENIX_APP_WEB.SessionView do + use PHOENIX_APP_WEB, :view +end +``` + +#### Router + +under you `scope "/"` add following + +```elixir +scope "/", PHOENIX_APP_WEB do + pipe_through :browser + ... + # Sign in + get "/sign-in", SessionController, :sign_in + post"/sign-in", SessionController, :create_session + + # Sign up + get "/sign-up", SessionController, :sign_up + post"/sign-up", SessionController, :create_user + + # Sign out + post "/sign-out", SessionController, :sign_out +end +``` + +#### Controller + +```elixir +defmodule PHOENIX_APP_WEB.SessionController do + use PHOENIX_APP_WEB, :controller + + alias PHOENIX_APP.Accounts + alias Accounts.User + + def sign_in(conn, _params) do + # If user is logged in and tries to connecto to "/sign-in" redirect him + if is_logged?(conn) do + redirect(conn, to: Routes.user_path(conn, :show)) + else + render(conn, "sign-in.html") + end + end + + def sign_up(conn, _params) do + # If user is logged in and tries to connecto to "/sign-up" redirect him + if is_logged?(conn) do + redirect(conn, to: Routes.user_path(conn, :show)) + else + changeset = Accounts.change_user(%User{}) + render(conn, "sign-up.html", changeset: changeset) + end + end + + defp is_logged?(conn), do: !!get_session(conn, :current_user_id) + + def create_session(conn, %{"session" => auth_params} = _params) do + user = Accounts.get_by_email(auth_params["email"]) + + case Bcrypt.check_pass(user, auth_params["password_hash"]) do + {:ok, user} -> + conn + |> put_session(:current_user_id, user.id) + |> put_flash(:info, "Sign in, successful!") + |> redirect(to: Routes.user_path(conn, :show)) + + {:error, _} -> + conn + |> put_flash(:error, "Invalid e-mail/password. Try again!") + |> redirect(to: Routes.session_path(conn, :sign_in)) + end + end + + def create_user(conn, %{"user" => user_params}) do + case Accounts.create_user(user_params) do + {:ok, user} -> + conn + |> put_session(:current_user_id, user.id) + |> put_flash(:info, "Sign up, successful!") + |> redirect(to: Routes.user_path(conn, :show)) + + {:error, %Ecto.Changeset{} = changeset} -> + render(conn, "sign-up.html", changeset: changeset) + end + end + + def sign_out(conn, _params) do + conn + |> delete_session(:current_user_id) + |> put_flash(:info, "Signed out.") + |> redirect(to: Routes.page_path(conn, :index)) + end +end + +``` \ No newline at end of file