Skip to content
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

User Authentication - Facebook & Google #10

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export FACEBOOK_CLIENT_ID=12341234
export FACEBOOK_CLIENT_SECRET=123145
export GUARDIAN_SECRET=23452345
export GOOGLE_CLIENT_ID=13241234
export GOOGLE_CLIENT_SECRET=3124234
export GOOGLE_REDIRECT_URI="http://localhost:4000/api/v1/auth/google/callback"
export DB_NAME_DEV=db_name
export DB_USERNAME_DEV=postgres
export DB_PASSWORD_DEV=password
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ npm-debug.log
# Alternatively, you may comment the line below and commit the
# secrets files as long as you replace their contents by environment
# variables.
/config/*.secret.exs
/config/*.secret.exs

.env
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

To start your Phoenix server:

* Install dependencies with `mix deps.get`
* Create and migrate your database with `mix ecto.create && mix ecto.migrate`
* Install Node.js dependencies with `cd assets && npm install`
* Start Phoenix endpoint with `mix phx.server`
1. Install dependencies with `mix deps.get`
1. Create and migrate your database with `mix ecto.create && mix ecto.migrate`
1. Install Node.js dependencies with `cd assets && npm install && cd ..`
1. Copy `.env.example` to `.env`
1. Fill out the Facebook and Google secrets.
1. Run `mix guardian.gen.secret` to get the `GUARDIAN_SECRET` hash.
1. Fill out the database connection information.
1. Start Phoenix endpoint with `mix phx.server`

Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.

Expand Down
44 changes: 43 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,52 @@ config :reel,
config :reel, ReelWeb.Endpoint,
url: [host: "localhost"],
secret_key_base: "FBtJXEM6wDTLRgdWowxZciXjYGaXagX2AKlIoTlMz42tSOMe+aqxHNY6cejEr+BS",
render_errors: [view: ReelWeb.ErrorView, accepts: ~w(html json)],
render_errors: [view: ReelWeb.ErrorView, accepts: ~w(html json json-api)],
pubsub: [name: Reel.PubSub,
adapter: Phoenix.PubSub.PG2]

# Change the json response type to json-api
config :phoenix, :format_encoders,
"json-api": Poison

config :plug, :types, %{
"application/vnd.api+json" => ["json-api"]
}

# Ueberauth Config for oauth
config :ueberauth, Ueberauth,
base_path: "/api/v1/auth",
providers: [
facebook: { Ueberauth.Strategy.Facebook, [profile_fields: "name,email,first_name,last_name"] },
google: { Ueberauth.Strategy.Google, [] },
identity: { Ueberauth.Strategy.Identity, [
callback_methods: ["POST"],
uid_field: :username,
nickname_field: :username,
] },
]

config :ueberauth, Ueberauth.Strategy.Facebook.OAuth,
client_id: System.get_env("FACEBOOK_CLIENT_ID"),
client_secret: System.get_env("FACEBOOK_CLIENT_SECRET")

# Ueberauth Strategy Config for Google oauth
config :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 configuration
config :guardian, Guardian,
allowed_algos: ["HS512"],
verify_module: Guardian.JWT,
issuer: "ReelWeb",
ttl: { 30, :days },
allowed_drift: 2000,
verify_issuer: true,
secret_key: System.get_env("GUARDIAN_SECRET"),
serializer: ReelWeb.GuardianSerializer

# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
Expand Down
6 changes: 3 additions & 3 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ config :phoenix, :stacktrace_depth, 20
# Configure your database
config :reel, Reel.Repo,
adapter: Ecto.Adapters.Postgres,
username: "postgres",
password: "reel",
database: "reel_dev",
username: System.get_env("DB_USERNAME_DEV"),
password: System.get_env("DB_PASSWORD_DEV"),
database: System.get_env("DB_NAME_DEV"),
hostname: "localhost",
template: "template0",
pool_size: 10
11 changes: 7 additions & 4 deletions lib/reel/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ defmodule Reel.User do
use Reel, :model

schema "users" do
field :auth_provider, :string
field :email, :string
field :facebook_id, :string
field :facebook_picture, :string
field :auth_provider_id, :string
field :avatar, :string
field :first_name, :string
field :is_private, :boolean, default: false
field :last_name, :string
field :username, :string

belongs_to :last_viewed_group, Group

has_one :media_personality, Person
Expand All @@ -25,7 +27,8 @@ defmodule Reel.User do
@doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:username, :first_name, :last_name, :email, :facebook_id, :facebook_picture, :is_private])
|> validate_required([:username, :first_name, :last_name, :email, :facebook_id, :facebook_picture, :is_private])
|> cast(attrs, [:username, :first_name, :last_name, :email, :auth_provider_id, :avatar, :is_private, :auth_provider])
|> validate_required([:username, :first_name, :last_name, :email, :auth_provider_id, :avatar, :is_private, :auth_provider])
|> unique_constraint(:email)
end
end
2 changes: 2 additions & 0 deletions lib/reel_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ defmodule ReelWeb do
def controller do
quote do
use Phoenix.Controller, namespace: ReelWeb
import Ecto.Query, only: [first: 1, where: 2]
alias Reel.Repo
import Plug.Conn
import ReelWeb.Router.Helpers
import ReelWeb.Gettext
Expand Down
35 changes: 1 addition & 34 deletions lib/reel_web/context.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,5 @@ defmodule ReelWeb.Context do
def call(conn, _) do
conn
end
#
# def authorize(auth_token) do
# InjectDetect.State.User.find(auth_token: auth_token)
# |> case do
# nil -> {:error, "Invalid authorization token"}
# user -> {:ok, user.id}
# end
# end
#
# def build_context(conn) do
# with ["Bearer " <> auth_token] <- get_req_header(conn, "authorization"),
# {:ok, user_id} <- authorize(auth_token)
# do
# {:ok, %{user_id: user_id}}
# else
# [] -> {:ok, %{}}
# error -> error
# end
# end
#
# def call(conn, _) do
# case build_context(conn) do
# {:ok, context} ->
# put_private(conn, :absinthe, %{context: context})
# {:error, reason} ->
# conn
# |> send_resp(403, reason)
# |> halt()
# _ ->
# conn
# |> send_resp(400, "Bad Request")
# |> halt()
# end
# end

end
180 changes: 180 additions & 0 deletions lib/reel_web/controllers/auth_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
defmodule ReelWeb.AuthController do
@moduledoc """
Auth controller responsible for handling Ueberauth responses
"""
use ReelWeb, :controller

alias Reel.User
alias Ueberauth.Auth

plug Ueberauth

def delete(conn, _params) do
# Sign out the user
conn
|> put_status(200)
|> Guardian.Plug.sign_out(conn)
end

def callback(conn, %{
"provider" => "facebook"
} = params) do
auth = conn.assigns.ueberauth_auth
info = conn.assigns.ueberauth_auth.info
sign_in_user(conn, %{
"data" => %{
"type" => "auth",
"attributes" => %{
"token" => auth.credentials.token,
"email" => info.email,
"first_name" => info.first_name,
"last_name" => info.last_name,
"avatar" => info.image,
"auth_provider" => "facebook"
}
}
})
end

def callback(conn, %{
"provider" => "google"
} = params) do
auth = conn.assigns.ueberauth_auth
info = conn.assigns.ueberauth_auth.info
sign_in_user(conn, %{
"data" => %{
"type" => "auth",
"attributes" => %{
"token" => auth.credentials.token,
"email" => info.email,
"first_name" => info.first_name,
"last_name" => info.last_name,
"avatar" => info.image,
"auth_provider" => "google"
}
}
})
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(ReelWeb.ErrorView, "401.json-api")
end

def sign_in_user(conn, %{
"data" => %{
"type" => "auth",
"attributes" => %{
"token" => token,
"email" => email,
"first_name" => first_name,
"last_name" => last_name,
"avatar" => avatar
}
}
}) 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: ^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(ReelWeb.ErrorView, "401.json-api")
end
rescue
e ->
IO.inspect e

redirect conn, to: "/signup"
# Sign the user up
# sign_up_user(conn, %{
# "data" => %{
# "type" => "auth",
# "attributes" => %{
# "token" => token,
# "email" => email,
# "first_name" => first_name,
# "last_name" => last_name,
# "avatar" => avatar
# }
# }
# })
end
end

def signup(conn, _params)
render conn, "index.html"
end

def confirm_signup(conn, params)
require IEx; IEx.pry
sign_up_user(conn, params)
end

def sign_up_user(conn, %{
"data" => %{
"type" => "auth",
"attributes" => %{
"token" => token,
"email" => email,
"first_name" => first_name,
"last_name" => last_name,
"avatar" => avatar,
"username" => username,
"auth_provider" => auth_provider
}
}
}) do
require IEx; IEx.pry
changeset = User.changeset %User{}, %{
email: email,
avatar: avatar,
first_name: first_name,
last_name: last_name,
username: username,
auth_provider: auth_provider
}

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(ReelWeb.ErrorView, "422.json-api")
end
end

def unauthenticated(conn, _params) do
conn
|> put_status(401)
|> render(ReelWeb.ErrorView, "401.json-api")
end
end
Loading