README.md

# KeenAuth

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `keen_auth` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:keen_auth, "~> 0.1.0"}
  ]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/keen_auth](https://hexdocs.pm/keen_auth).

## Required steps

Starting from a new project with ecto

1. Add `keen_auth` dependency
2. Add configuration to `config.exs`
    
    ```elixir
    common_auth_processor = DemoWeb.Auth.Processor
    
    config :keen_auth,
      # storage: KeenAuthDemoWeb.Auth.SessionStorage,
      strategies: [
        aad: [
          strategy: Assent.Strategy.AzureAD,
          mapper: KeenAuth.Mappers.AzureAD,
          processor: common_auth_processor,
          config: [
            tenant_id: "REPLACE_WITH_PROPPER_VALUE",
            client_id: "REPLACE_WITH_PROPPER_VALUE",
            client_secret: "REPLACE_WITH_PROPPER_VALUE",
            redirect_uri: "http://localhost:4000/aad/callback"
          ]
        ],
        github: [
          strategy: Assent.Strategy.Github,
          mapper: KeenAuth.Mappers.Github,
          processor: common_auth_processor,
          config: [
            client_id: "REPLACE_WITH_PROPPER_VALUE",
            client_secret: "REPLACE_WITH_PROPPER_VALUE",
            redirect_uri: "https://localhost:4000/auth/github/callback"
          ]
        ],
        facebook: [
          strategy: Assent.Strategy.Facebook,
          mapper: KeenAuth.Mappers.Facebook,
          processor: common_auth_processor,
          config: [
            client_id: "REPLACE_WITH_PROPPER_VALUE",
            client_secret: "REPLACE_WITH_PROPPER_VALUE",
            redirect_uri: "https://localhost:4000/auth/facebook/callback"
          ]
        ]
    ```
    then add ``` plug KeenAuth.Plug ``` to endpoint above router
    
3. Replace cookie session storage with ETS
    1. Make sure to create session ETS table when the application starts
        
        ```elixir
        def start(_, _) do
        	children = [
        		# ...
        	]
        
        	create_session_table()
        
        	opts = [strategy: :one_for_one, name: Demo.Supervisor]
          Supervisor.start_link(children, opts)
        end
        
        defp create_session_table() do
          :ets.new(:session, [:named_table, :public, read_concurrency: true])
        end
        ```
        
    2. Reconfigure `@session_options` in `endpoint.ex` to ETS
        
        ```elixir
        @session_options [
          store: :ets,
          table: :session,
          key: "_test_key",
          signing_salt: "EdtoEWM7"
        ]
        ```
        
4. Modify router
    1. Add this line to the beginning of router
        
        ```elixir
        require KeenAuth
        ```
        
    2. Add following pipelines
        
        ```elixir
        pipeline :authentication do
          plug :fetch_session
          plug :put_root_layout, {KeenAuthDemoWeb.LayoutView, :root}
        end
        
        pipeline :authorization do
          plug :fetch_session
          plug KeenAuth.Plug.FetchUser
        end
        ```
        
    3. Add `/auth` subroute
        
        ```elixir
        scope "/auth" do
          pipe_through :authentication
        
          KeenAuth.authentication_routes()
        end
        ```
        
5. Enable HTTPS for development (as required by Facebook)
    1. `mix phx.gen.cert`
    2. Replace `http` configuration under Endpoint in `config/dev.exs` with `https`
        
        ```elixir
        https: [
          ip: {127, 0, 0, 1},
          port: 4000,
          cipher_suite: :strong,
          keyfile: "priv/cert/selfsigned_key.pem",
          certfile: "priv/cert/selfsigned.pem"
        ],
        ```
        

## Optional steps

- Add login buttons
    - Add `/sign-in` route to router
        
        ```elixir
        get "/sign-in", PageController, :sign_in
        ```
        
    - Add `sign_in` route to `PageController`
        
        ```elixir
        defmodule DemoWeb.PageController do
        	alias KeenAuth.Config
        	
        	def sign_in(conn, _params) do
        	  if KeenAuth.authenticated?(conn) do
        	    redirect(conn, to: "/")
        	  else
        	    render(conn, "sign_in.html")
        	  end
        	end
        end
        ```
        
    - Add `sign_in.html.heex` template
        
        ```html
        <style>
        	.sign-in-options {
        			display: flex;
        			justify-content: center;
        			align-items: center;
        	}
        
        	.sign-in-options .option {
        			flex: 1;
        	}
        </style>
        
        <section class="row">
        	<article class="column">
        		<h2>Sign in options:</h2>
        
        		<div class="sign-in-options">
        			<div class="option">
        				<a href={Routes.authentication_path(@conn, :new, :aad, redirect_to: "/")}>
        					Azure
        				</a>
        			</div>
        			<div class="option">
        				<a href={Routes.authentication_path(@conn, :new, :github, redirect_to: "/")}>
        					Github
        				</a>
        			</div>
        			<div class="option">
        				<a href={Routes.authentication_path(@conn, :new, :facebook, redirect_to: "/")}>
        					Facebook
        				</a>
        			</div>
        		</div>
        	</article>
        </section>
        ```
        
        1. Create processor module (`DemoWeb.Auth.Processor`) implementing `KeenAuth.Processor` behavior
            
            ```elixir
            defmodule TestWeb.Auth.Processor do
              @behaviour KeenAuth.Processor
            
              import Plug.Conn, only: [put_session: 3]
            
              require Logger
            
              @impl true
              def process(conn, provider, response) do
                Logger.debug("Processing OAuth response for #{provider}", response: inspect(response))
            
                {:ok, put_session(conn, :oauth_response, response), response}
              end
            end
            ```