Last active
June 29, 2016 22:47
-
-
Save andrewhao/bb20b9a714e71ceac4880fea0628781a to your computer and use it in GitHub Desktop.
Rails and Phoenix Session Sharing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# elixir_buildpack.config | |
config_vars_to_export=(SECRET_KEY_BASE SESSION_ENCRYPTED_COOKIE_SALT SESSION_ENCRYPTED_SIGNED_COOKIE_SALT DOMAIN) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%{"_csrf_token" => "ELeSt4MBUINKi0STEBpslw3UevGZuVLUx5zGVP5NlQU=", | |
"session_id" => "17ec9b696fe76ba4a777d625e57f3521", | |
"warden.user.user.key" => [[2], "$2a$10$R/3NKl9KQViQxY8eoMCIp."]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# lib/phoenix_app/endpoint.ex | |
defmodule PhoenixApp.Endpoint do | |
plug Plug.Session, | |
# Remove the original cookie store that comes with Phoenix, out of the box. | |
# store: :cookie, | |
# key: "_phoenix_app_key", | |
# signing_salt: "M8emDP0h" | |
store: PlugRailsCookieSessionStore, | |
# Decide on a shared key for your cookie. Oftentimes, this should | |
# mirror your Rails app session key | |
key: "_rails_app_session", | |
secure: true, | |
encrypt: true, | |
# Specifies the matching rules on the hostname that this cookie will be valid for | |
domain: ".#{System.get_env("DOMAIN")}", | |
signing_salt: System.get_env("SESSION_ENCRYPTED_SIGNED_COOKIE_SALT"), | |
encryption_salt: System.get_env("SESSION_ENCRYPTED_COOKIE_SALT"), | |
key_iterations: 1000, | |
key_length: 64, | |
key_digest: :sha, | |
# Specify a JSON serializer to use on the session | |
serializer: Poison | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# mix.exs | |
defmodule PhoenixApp | |
defp deps do | |
# snip | |
{:plug_rails_cookie_session_store, "~> 0.1"}, | |
# snip | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/models/session.ex | |
defmodule PhoenixApp.Session do | |
use PhoenixApp.Web, :controller | |
def current_user(conn) do | |
# Our app's concept of a User is merely whatever is stored in the | |
# Session key. In the future, we could then use this as the delegation | |
# point to fetch more details about the user from a backend store. | |
case load_user(conn) do | |
{:ok, user_id} -> user_id | |
{:error, :not_found} -> nil | |
end | |
end | |
def logged_in?(conn) do | |
!!current_user(conn) | |
end | |
def load_user(conn) do | |
# => The Warden user storage scheme: [user_id, password_hash_truncated] | |
# [[1], "$2a$10$vnx35UTTJQURfqbM6srv3e"] | |
warden_key = conn |> get_session("warden.user.user.key") | |
case warden_key do | |
[[user_id], _] -> {:ok, user_id} | |
_ -> {:error, :not_found} | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/models/session.ex | |
defmodule PhoenixApp.Session do | |
use PhoenixApp.Web, :controller | |
alias PhoenixApp.User | |
def current_user(conn) do | |
case load_user(conn) do | |
# Changed current_user/1 to now return a User or a nil. | |
{:ok, user_id} -> user_id |> User.fetch | |
{:error, :not_found} -> nil | |
end | |
end | |
# snip | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config/initializer/session_store.rb | |
# Use cookie session storage in JSON format. Here, we scope the cookie to the root domain. | |
Rails.application.config.session_store :cookie_store, key: '_rails_app_session', domain: ".#{ENV['DOMAIN']}" | |
Rails.application.config.action_dispatch.cookies_serializer = :json | |
# These salts are optional, but it doesn't hurt to explicitly configure them the same between the two apps. | |
Rails.application.config.action_dispatch.encrypted_cookie_salt = ENV['SESSION_ENCRYPTED_COOKIE_SALT'] | |
Rails.application.config.action_dispatch.encrypted_signed_cookie_salt = ENV['SESSION_ENCRYPTED_SIGNED_COOKIE_SALT'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/controllers/some_api_resource_controller.ex | |
defmodule PhoenixApp.SomeApiResourceController do | |
use PhoenixApp.Web, :controller | |
def index(conn, _params) do | |
{:ok, user_id} = load_user(conn) | |
conn | |
|> assign(:user_id, user_id) | |
|> render("index.html") | |
end | |
plug :verify_session | |
# If we've found a user, then allow the request to continue. | |
# Otherwise, halt the request and return a 401 | |
defp verify_session(conn, _) do | |
case load_user(conn) do | |
{:ok, user_id} -> conn | |
{:error, _} -> conn |> send_resp(401, "Unauthorized") |> halt | |
end | |
end | |
defp load_user(conn) do | |
# => The Warden user storage scheme: [user_id, password_hash_truncated] | |
# [[1], "$2a$10$vnx35UTTJQURfqbM6srv3e"] | |
warden_key = conn |> get_session("warden.user.user.key") | |
case warden_key do | |
[[user_id], _] -> {:ok, user_id} | |
_ -> {:error, :not_found} | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/controllers/some_api_resource_controller.ex | |
defmodule PhoenixApp.SomeApiResourceController do | |
use PhoenixApp.Web, :controller | |
alias PhoenixApp.Session | |
def index(conn, _params) do | |
IO.inspect conn.private.plug_session | |
user_id = Session.current_user(conn) | |
conn | |
|> assign(:user_id, user_id) | |
|> render("index.html") | |
end | |
plug :verify_session | |
# Future refinements could extract this into its own Plug file. | |
defp verify_session(conn, _) do | |
case Session.logged_in?(conn) do | |
false -> conn |> send_resp(401, "Unauthorized") |> halt | |
_ -> conn | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/models/user.ex | |
defmodule PhoenixApp.User do | |
# Gets some user identity information like email, avatar image. | |
# For this example, we'll use a random user generator. | |
# | |
# This example hits an API, but this could just as easily be something that hits | |
# the database, or Redis, or some cache. | |
def fetch(user_id) do | |
%{ body: body } = HTTPotion.get("https://randomuser.me/api?seed=#{user_id}") | |
[result | _ ] = body |> Poison.decode! |> Map.get("results") | |
result | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# web/web.ex | |
defmodule PhoenixApp.Web do | |
def view do | |
quote do | |
# snip | |
import PhoenixApp.Session | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment