2015-09-02
Luke Imhoff
luke_imhoff@rapid7.com | Kronic.Deth@gmail.com | |
@limhoff-r7 | @KronicDeth | |
@KronicDeth |
Source | Linux | Mac | Windows |
---|---|---|---|
nodejs.org |
tar.gz
|
pkg
|
msi
|
homebrew | brew install node |
||
apt-get |
sudo apt-get install nodejs-legacy
|
mix local.hex
mix archive.install https://github.com/phoenixframework/phoenix/releases/download/v0.17.0/phoenix_new-0.17.0.ez
mix phoenix.new
Options
mix phoenix.new
Fetch and Install Dependencies
mix phoenix.new
Options
Option | Description | Format | Example |
---|---|---|---|
--app APP
|
The name of the OTP application | Atom |
routing_securely_with_phoenix_framework
|
--database DATABASE
|
Specify the database adapter for ecto |
|
|
--module MODULE
|
The name of the base module in the generated skeleton | Alias |
RoutingSecurelyWithPhoenixFramework
|
--no-ecto
|
Do not generate Ecto files for the model layer | ||
--no-brunch
|
Do not generate brunch files for static asset building |
mix phoenix.new
Error | Solution |
---|---|
mix deps.get
|
|
npm install
|
Environment | Module | key | File | Source Controlled? |
---|---|---|---|---|
All |
Endpoint
|
secret_key_base
|
config/config.exs
|
Yes |
dev
|
Repo
|
password
|
config/dev.exs
|
Yes |
prod
|
Endpoint
|
secret_key_base
|
config/prod.secret.exs
|
No |
prod
|
Repo
|
password
|
config/prod.secret.exs
|
No |
test
|
Repo
|
password
|
config/test.exs
|
Yes |
secret_key_base
secret_key_base
iex(1)> length = 64
64
iex(2)> :crypto.strong_rand_bytes(length) |> Base.encode64 |> binary_part(0, length)
Termianl | pgAdmin | |
---|---|---|
1 |
createuser --createdb --encrypted --no-createrole --no-superuser --pwprompt USERNAME
|
Start pgAdmin3 |
2 | Enter password | Connect to server |
3 | Enter password (again) | Right-click Login Roles |
4 | Click New Login Role | |
5 | Enter username |
|
6 | Change to Definition Tab | |
7 | Enter password | |
8 | Enter password (again) | |
9 | Change to Role Privileges Tab | |
10 | Click "Can create database" | |
11 | Click OK |
dev
Configuration
prod
Configuration
test
Configuration
Option | Argument | Description |
---|---|---|
genrsa
|
Generate RSA key | |
-des3
|
Password protect the private key | |
-out
|
certificate-authority.key
|
Private Key file |
4096 | Number of bits in RSA key |
Option | Argument | Description |
---|---|---|
req
|
X.509 Certificate Signing Request (CSR) Management | |
-new
|
New Certificate Request | |
-x509
|
Self-signed certificate | |
-days
|
365
|
Number of days certificate is valid |
-key
|
certificate-authority.key
|
Input file name for private key used for signing |
-out
|
certificate-authority.crt
|
Output file for self-signed certificate |
-sha256
|
Sign using SHA256 instead of SHA1 |
openssl x509 -in certificate-authority.crt -noout -text
openssl genrsa -out server-${MIX_ENV}.key 4096
openssl req -new -key server-${MIX_ENV}.key -out server-${MIX_ENV}.csr
Option | Argument | Description |
---|---|---|
x509
|
Certificate display and signing utility | |
‑CA
|
certificate‑authority.crt
|
The Certificate Authority certificate |
‑CAcreateserial
|
Create file for keeping track of CA issued certificate serial numbers if it does not exist and assign this certificate the next serial number. | |
‑CAkey
|
certificate‑authority.key
|
Certificate Authority private Key for signing request |
‑days
|
90
|
Days the server certificate is valid |
‑in
|
server‑${MIX_ENV}.csr
|
Request to be signed |
‑out
|
server‑${MIX_ENV}.crt
|
Signed certificate output file |
‑req
|
Sign a certificate request | |
‑sha256
|
Sign with SHA256 instead of SHA1 |
certificate-authority.crt
/etc/hosts
Add the following lines
127.0.0.1 dev.routing-securely-with-phoenix-framework.localhost
127.0.0.1 prod.routing-securely-with-phoenix-framework.localhost
127.0.0.1 test.routing-securely-with-phoenix-framework.localhost
dev
Configurationprod
Configurationtest
ConfigurationUser
model
new
new
Template
create
new
new
Template
create
delete
User
modelArgument | Description |
---|---|
User
|
Module name |
users
|
Table name |
name
|
Column name |
password_hash
|
Column name |
defmodule RoutingSecurelyWithPhoenixFramework.Repo.Migrations.CreateUser do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string, null: false
add :password_hash, :string, null: false
timestamps
end
create unique_index(:users, [:name])
end
end
defmodule RoutingSecurelyWithPhoenixFramework.User do
use RoutingSecurelyWithPhoenixFramework.Web, :model
schema "users" do
field :name, :string
field :password, :string, virtual: true
field :password_confirmation, :string, virtual: true
field :password_hash, :string
timestamps
end
end
defmodule RoutingSecurelyWithPhoenixFramework.User do
@minimum_password_length 16
@optional_fields ~w()
@required_fields ~w(name password password_confirmation)
@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> validate_length(:password, min: @minimum_password_length)
|> validate_length(:password_confirmation, min: @minimum_password_length)
|> validate_confirmation(:password)
|> unique_constraint(:name)
end
end
defmodule RoutingSecurelyWithPhoenixFramework.User do
@doc """
Generates a password for the user changeset and stores it to the changeset as password_hash.
"""
def generate_password(changeset) do
put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(changeset.params["password"]))
end
end
defmodule RoutingSecurelyWithPhoenixFramework.Router do
use RoutingSecurelyWithPhoenixFramework.Web, :router
scope "/", RoutingSecurelyWithPhoenixFramework do
pipe_through :browser # Use the default browser stack
get "/registration", RegistrationController, :new
post "/registration", RegistrationController, :create
end
end
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationController do
use RoutingSecurelyWithPhoenixFramework.Web, :controller
alias RoutingSecurelyWithPhoenixFramework.User
plug :scrub_params, "user" when action in [:create]
end
new
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationController do
def new(conn, _params) do
changeset = User.changeset(%User{})
render conn, changeset: changeset
end
end
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationView do
use RoutingSecurelyWithPhoenixFramework.Web, :view
end
new
Template
<h3>Registration</h3>
<%= form_for @changeset, registration_path(@conn, :create), fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Name</label>
<%= text_input f, :name, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= password_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<label>Password Confirmation</label>
<%= password_input f, :password_confirmation, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Register", class: "btn btn-primary" %>
<%= link("Login", to: session_path(@conn, :new), class: "btn btn-success pull-right") %>
</div>
<% end %>
create
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationController do
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
if changeset.valid? do
case changeset |> User.generate_password |> Repo.insert do
{:ok, user} ->
conn
|> put_flash(:info, "Successfully registered and logged in")
|> put_session(:current_user_id, user_id)
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render conn, "new.html", changeset: changeset
end
else
render conn, "new.html", changeset: changeset
end
end
end
defmodule RoutingSecurelyWithPhoenixFramework.Router do
scope "/", RoutingSecurelyWithPhoenixFramework do
get "/pages", PageController, :index
end
end
defmodule RoutingSecurelyWithPhoenixFramework.PageController do
plug RoutingSecurelyWithPhoenixFramework.Plug.Authenticate
end
defmodule RoutingSecurelyWithPhoenixFramework.Plug.Authenticate do
import RoutingSecurelyWithPhoenixFramework.Router.Helpers
alias RoutingSecurelyWithPhoenixFramework.Repo
alias RoutingSecurelyWithPhoenixFramework.User
def init(opts) do
opts
end
def call(conn, _opts) do
assign_current_user(conn)
end
defp assign_current_user(conn = %Plug.Conn{}) do
assign_current_user(conn, Plug.Conn.get_session(conn, :current_user_id))
end
defp assign_current_user(conn, id) when is_integer(id) do
assign_current_user(conn, Repo.get(User, id))
end
defp assign_current_user(conn, user = %User{}) do
Plug.Conn.assign(conn, :current_user, user)
end
defp assign_current_user(conn, _), do: redirect_to_sign_in(conn)
defp redirect_to_sign_in(conn) do
conn
|> Phoenix.Controller.put_flash(
:error,
'You need to be signed in to view this page'
)
|> Phoenix.Controller.redirect(to: session_path(conn, :new))
|> Plug.Conn.halt
end
end
defmodule RoutingSecurelyWithPhoenixFramework.Router do
scope "/", RoutingSecurelyWithPhoenixFramework do
get "/", SessionController, :new
post "/login", SessionController, :create
get "/logout", SessionController, :delete
end
end
defmodule RoutingSecurelyWithPhoenixFramework.SessionController do
use RoutingSecurelyWithPhoenixFramework.Web, :controller
alias RoutingSecurelyWithPhoenixFramework.User
plug :scrub_params, "user" when action in [:create]
end
new
defmodule RoutingSecurelyWithPhoenixFramework.SessionController do
def new(conn, _params) do
render conn, changeset: User.changeset(%User{})
end
end
defmodule RoutingSecurelyWithPhoenixFramework.SessionView do
use RoutingSecurelyWithPhoenixFramework.Web, :view
end
new
Template
<h3>Login</h3>
<%= form_for @changeset, session_path(@conn, :create), fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Name</label>
<%= text_input f, :name, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= password_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Login", class: "btn btn-primary" %>
<%= link("Sign Up", to: registration_path(@conn, :new), class: "btn btn-success pull-right") %>
</div>
<% end %>
create
defmodule RoutingSecurelyWithPhoenixFramework.SessionController do
def create(conn, %{"user" => user_params}) do
if is_nil(user_params["name"]) do
nil
else
Repo.get_by(User, name: user_params["name"])
end
|> sign_in(user_params["password"], conn)
end
# Private Functions
@sign_in_error "Name or password are incorrect."
defp sign_in(user, _password, conn) when is_nil(user) do
conn
|> put_flash(:error, @sign_in_error)
|> render "new.html", changeset: User.changeset(%User{})
end
defp sign_in(user, password, conn) when is_map(user) do
if Comeonin.Bcrypt.checkpw(password, user.password_hash) do
conn
|> put_session(:current_user_id, user.id)
|> put_flash(:info, "You are now signed in.")
|> redirect(to: page_path(conn, :index))
else
conn
|> put_flash(:error, @sign_in_error)
|> render "new.html", changeset: User.changeset(%User{})
end
end
end
<!DOCTYPE html>
<html lang="en">
<body>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container">
<div id="navbar" class="navbar-collapse collapse">
<div class="navbar-form navbar-right">
<%= case @conn.assigns[:current_user] do %>
<% nil -> %>
<%= link "Login", class: "btn btn-success", role: "button", to: session_path(@conn, :new) %>
<% current_user -> %>
<span class="label label-info">
<%= current_user.name %>
</span>
<%= link "Logout", class: "btn btn-danger", role: "button", to: session_path(@conn, :delete) %>
<% end %>
</div>
</div><!--/.navbar-collapse -->
</div>
</nav>
</body>
</html>
delete
defmodule RoutingSecurelyWithPhoenixFramework.SessionController do
def delete(conn, _) do
delete_session(conn, :current_user_id)
|> put_flash(:info, "You have been logged out")
|> redirect(to: session_path(conn, :new))
end
end
GET
requestsGET
requests MUST NOT change state
<h3>Registration</h3>
<%= form_for @changeset, registration_path(@conn, :create), fn f -> %>
<form accept-charset="UTF-8" action="/registration" method="post">
<input name="_csrf_token" type="hidden" value="e3JoLCxfDyBXFHYaNVgjOwEeQx4uNgAA000tv4wt1p1bv4wOMVtHiQ==">
_csrf_token
(param) == _csrf_token
(session)
<div id="messages" class="container"></div>
<div id="footer">
<div class="container">
<div class="row">
<div class="col-sm-12">
<label for="message-input">Your Message:</label>
<textarea id="message-input" class="form-control">
</textarea>
</div>
</div>
</div>
</div>
socket.js
import {Socket} from "deps/phoenix/web/static/js/phoenix"
import $ from "jquery"
let socket = new Socket("/socket")
let socketToken = $("meta[name='socket_token']").attr("content")
if (socketToken !== undefined) {
socket.connect({token: socketToken})
// Now that you are connected, you can join the lobby
let channel = socket.channel("rooms:lobby", {})
let messageInput = $("#message-input")
messageInput.on("keypress", event => {
if (event.keyCode === 13) {
channel.push("new_message", {body: messageInput.val()})
messageInput.val("")
}
})
let messagesContainer = $("#messages")
channel.on("new_message", payload => {
messagesContainer.append(
`
${Date()}
${payload.user.name}
${payload.body}
`
)
})
channel.join()
}
export default socket
socket_token
meta
tag
<!DOCTYPE html>
<html lang="en">
<head>
<%= case @conn.assigns[:current_user] do %>
<% nil -> %>
<% current_user -> %>
<%= tag :meta,
content: Phoenix.Token.sign(@conn, "user", {current_user.id, current_user.socket_token}),
name: "socket_token" %>
<% end %>
</head>
</html>
socket_token
to users
defmodule RoutingSecurelyWithPhoenixFramework.Repo.Migrations.AddChannelTokenToUser do
use Ecto.Migration
def down do
alter table(:users) do
remove :socket_token
end
end
def up do
alter table(:users) do
# add socket_token as null because it needs to be populated first
add :socket_token, :string, null: true
end
# populate with secure random value using same algorithm as `mix phoenix.gen.secret`
execute "CREATE EXTENSION IF NOT EXISTS pgcrypto"
execute "UPDATE users SET socket_token = encode(gen_random_bytes(64), 'base64') WHERE socket_token IS NULL"
alter table(:users) do
modify :socket_token, :string, null: false
end
end
end
socket_token
to User
schema
defmodule RoutingSecurelyWithPhoenixFramework.User do
schema "users" do
field :socket_token, :string
end
end
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationController do
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
if changeset.valid? do
full_changeset = User.full_changeset(changeset)
case Repo.insert(full_changeset) do
{:ok, user} ->
conn
|> put_flash(:info, "Successfully registered and logged in")
|> put_session(:current_user_id, user.id)
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render conn, "new.html", changeset: changeset
end
else
render conn, "new.html", changeset: changeset
end
end
end
User.full_changeset/1
defmodule RoutingSecurelyWithPhoenixFramework.User do
@socket_token_length 64
@doc """
Fills `changeset` with generated columns.
"""
def full_changeset(changeset) do
changeset
|> generate_password
|> generate_socket_token
end
@doc """
Generates a channel token for the user changeset.
"""
def generate_socket_token(changeset) do
put_change(changeset, :socket_token, generate_secret(@socket_token_length))
end
# Private Functions
@doc """
Generates a random secret string of the given `length`
"""
defp generate_secret(length) do
:crypto.strong_rand_bytes(length) |> Base.encode64 |> binary_part(0, length)
end
end
UserSocket.connect/2
defmodule RoutingSecurelyWithPhoenixFramework.UserSocket do
@seconds_per_minute 60
@minutes_per_hour 60
@hours_per_day 24
@days_per_week 7
@token_max_age 2 * @days_per_week * @hours_per_day * @minutes_per_hour *
@seconds_per_minute
def connect(%{"token" => token}, socket) do
case Phoenix.Token.verify(socket, "user", token, max_age: @token_max_age) do
{:ok, {id, socket_token}} ->
connect_user_id_and_socket_token(socket, id, socket_token)
{:error, reason} ->
:error
end
end
defp connect_user(socket, user) do
socket = assign(socket, :user_id, user.id)
{:ok, socket}
end
defp connect_user_id_and_socket_token(socket, id, socket_token) do
user = Repo.get_by!(User, id: id, socket_token: socket_token)
connect_user socket, user
end
end
UserSocket.id/1
defmodule RoutingSecurelyWithPhoenixFramework.UserSocket do
@doc """
Group all sockets for a given user together so they can be disconnected.
# Disconnecting a compromised user
iex> RoutingSecurelyWithPhoenixFramework.Endpoint.broadcast("user_sockets:" <> user_id, "disconnect", %{})
"""
def id(socket) do
"user_sockets:#{socket.assigns.user_id}"
end
end
RoomChannel
defmodule RoutingSecurelyWithPhoenixFramework.RoomChannel do
use RoutingSecurelyWithPhoenixFramework.Web, :channel
def join("rooms:lobby", _payload, socket) do
{:ok, socket}
end
@doc """
Get user from `socket` before handling event.
"""
def handle_in(event, params, socket) do
user = Repo.get! User, socket.assigns.user_id
handle_in(event, params, user, socket)
end
@doc """
Broadcast a message from one user to all users (including the originating user)
"""
def handle_in("new_message", %{"body" => body}, user, socket) do
broadcast! socket,
"new_message",
%{
body: body,
user: %{
name: user.name
}
}
{:noreply, socket}
end
end
<script>
$ = require("jquery");
userName = $("nav span.label-info").text().trim();
if (userName !== "Chuck") {
socketToken = $('meta[name="socket_token"]').attr("content");
phoenix = require("deps/phoenix/web/static/js/phoenix");
socket = new phoenix.Socket("/socket");
socket.connect({ token: socketToken});
channel = socket.channel("rooms:lobby", {});
channel.join().receive("ok", function (response) {
channel.push("new_message", { body: "Message sent by Chuck's XSS" });
});
}
</script>
Client-side
Server-side
defmodule RoutingSecurelyWithPhoenixFramework.RoomChannel do
@doc """
Broadcast a message from one user to all users (including the originating user)
"""
def handle_in("new_message", %{"body" => body}, user, socket) do
broadcast! socket,
"new_message",
%{
body: Plug.HTML.html_escape(body),
user: %{
name: Plug.HTML.html_escape(user.name)
}
}
{:noreply, socket}
end
end
Run server on a named node with a known cookie
iex --cookie $COOKIE --name server@$HOST -S mix phoenix.server
Connect with remote shell
iex --cookie $COOKIE --hidden --name console@$HOST --remsh server@$HOST
Setup some aliases
alias RoutingSecurelyWithPhoenixFramework.Endpoint
alias RoutingSecurelyWithPhoenixFramework.Repo
alias RoutingSecurelyWithPhoenixFramework.User
Delete attacker's account
user = Repo.get_by!(User, name: "Chuck")
Repo.delete!(user)
Disconnect attacker's sockets
Endpoint.broadcast("user_sockets:#{user.id}", "disconnect", %{})
Reset Password
user = Repo.get_by!(User, name: "Kronic Deth")
changeset = User.changeset user,
%{
"password" => new_password,
"password_confirmation" => new_password
}
Reset Socket Token
full_changeset = User.full_changeset(changeset)
Repo.update!(full_changeset)
Disconnect Kronic Deth's active sockets
Endpoint.broadcast("user_sockets:#{user.id}", "disconnect", %{})
secret_key_base
defmodule RoutingSecurelyWithPhoenixFramework.Plug.Authenticate
defp assign_current_user(conn = %Plug.Conn{}) do
assign_current_user(conn, Plug.Conn.get_session(conn, :current_user_id))
end
defp assign_current_user(conn, id) when is_integer(id) do
assign_current_user(conn, Repo.get(User, id))
end
defp assign_current_user(conn, user = %User{}) do
Plug.Conn.assign(conn, :current_user, user)
end
defp assign_current_user(conn, _), do: redirect_to_sign_in(conn)
defp redirect_to_sign_in(conn) do
conn
|> Phoenix.Controller.put_flash(
:error,
'You need to be signed in to view this page'
)
|> Phoenix.Controller.redirect(to: session_path(conn, :new))
|> Plug.Conn.halt
end
end
session_token
to users
defmodule RoutingSecurelyWithPhoenixFramework.Repo.Migrations.AddSessionTokenToUser do
use Ecto.Migration
def down do
alter table(:users) do
remove :session_token
end
end
def up do
alter table(:users) do
# add session_token as null because it needs to be populated first
add :session_token, :string, null: true
end
# populate with secure random value using same algorithm as `mix phoenix.gen.secret`
execute "CREATE EXTENSION IF NOT EXISTS pgcrypto"
execute "UPDATE users SET session_token = encode(gen_random_bytes(64), 'base64') WHERE session_token IS NULL"
alter table(:users) do
modify :session_token, :string, null: false
end
end
end
session_token
to User
schema
defmodule RoutingSecurelyWithPhoenixFramework.User do
schema "users" do
field :session_token, :string
end
end
User.full_changeset/1
defmodule RoutingSecurelyWithPhoenixFramework.User do
@token_length 64
@doc """
Fills `changeset` with generated columns.
"""
def full_changeset(changeset) do
changeset
|> generate_password
|> generate_token(:session)
|> generate_token(:socket)
end
@doc """
Generates a token to allow `type` credentials to be revoked.
"""
def generate_token(changeset, type) do
put_change(changeset, :"#{type}_token", generate_secret(@token_length))
end
end
Authenticate
with revocation
defmodule RoutingSecurelyWithPhoenixFramework.Plug.Authenticate do
@doc """
Stores `user` in session so that `call` can retrieve it.
"""
def put_session(conn, user) do
conn
|> Plug.Conn.put_session(:current_user_id, user.id)
|> Plug.Conn.put_session(:current_user_session_token, user.session_token)
end
# Private Functions
defp assign_current_user(conn = %Plug.Conn{}) do
assign_current_user conn,
Plug.Conn.get_session(conn, :current_user_id),
Plug.Conn.get_session(conn, :current_user_session_token)
end
defp assign_current_user(conn, id, session_token)
when is_integer(id) and is_binary(session_token) do
assign_current_user conn,
Repo.get_by(User, id: id, session_token: session_token)
end
defp assign_current_user(conn, _, _), do: redirect_to_sign_in(conn)
defp assign_current_user(conn, user = %User{}) do
Plug.Conn.assign(conn, :current_user, user)
end
defp assign_current_user(conn, _), do: redirect_to_sign_in(conn)
end
RegistrationController.create
defmodule RoutingSecurelyWithPhoenixFramework.RegistrationController do
def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
if changeset.valid? do
full_changeset = User.full_changeset(changeset)
case Repo.insert(full_changeset) do
{:ok, user} ->
conn
|> put_flash(:info, "Successfully registered and logged in")
|> Authenticate.put_session(user)
|> redirect(to: page_path(conn, :index))
{:error, changeset} ->
render conn, "new.html", changeset: changeset
end
else
render conn, "new.html", changeset: changeset
end
end
end
SessionController.sign_in
defmodule RoutingSecurelyWithPhoenixFramework.SessionController do
defp sign_in(user, password, conn) when is_map(user) do
if Comeonin.Bcrypt.checkpw(password, user.password_hash) do
conn
|> put_flash(:info, "You are now signed in.")
|> Authenticate.put_session(user)
|> redirect(to: page_path(conn, :index))
else
conn
|> put_flash(:error, @sign_in_error)
|> render "new.html", changeset: User.changeset(%User{})
end
end
end
Vulnerability | Protection |
---|---|
Database password being disclosed from Github search | config/*.secret.exs |
Cookie secret_key_base being disclosed from Github search |
config/*.secret.exs |
Password being sniffed on the network | TLS |
Cookies being sniffed on the network | TLS |
CSRF in HTML forms | form_for adds hidden _csrf_token input |
XSS in HTML views | Phoenix.HTML does escaping for *.html.eex |
XSS in channel messages | Plug.Conn.html_escape explicitly |
Session can't be revoked | Store token in session and check against database in authentication plug |
Socket access can't be revoked | Store token in signed-token and check against database in connect/2 |
Socket access can't be revoked until new websocket connection |
Non-nil id/1 and Endpoint.broadcast(id, "disconnect", %{})
|
mix help phoenix.new
Phoenix.Token