Header-based authentication for REST API in Phoenix - rest

I'm implementing a rest api in Elixir. An api-key is passed to each request to authenticate a user. In a plug I have this:
defmodule MyApp.ApiSecurity do
def init(options) do
options
end
def call(conn, _opts) do
# checking if "api-key" headers exists
# and key is valid
# .... what's next?
# if it's a) valid
# b) invalid or there's no "api-key" header
# ???
end
end
I know how to implement it for a normal, form-based authentication with a state and session. But in rest api there's no session. Then, what should I do? In other words, What should be in the rest of the function "call" when a) an api-key is valid b) invalid?

If the key is invalid or not present, you'd normally send the error message with a proper error status code and then call Plug.Conn.halt/1, which will stop this request from going further through the plug pipeline. If they key is valid, you'd probably want to assign some value to the conn, (e.g. user_id) which the rest of your application can use.
For example:
def call(conn, _opts) do
case authenticate(conn) do
{:ok, user_id} ->
conn
|> assign(:user_id, user_id)
:error ->
conn
|> send_resp(401, "Unauthenticated")
|> halt()
end
end
end
Now, any plugs that are plugged after this one can be sure that there exists a valid user_id in conn.assigns and can make use of it.
For a more real-world approach, you can see how guardian does this:
Guardian.Plug.EnsureResource
Guardian.Plug.ErrorHandler

Related

Flutter + Django OAuth integration

I am using Flutter as front end and Django for back end purpose. I am trying to integrate Google and Facebook OAuth in the app and using some flutter libraires I am able to fetch user details and access token in front end. Now the question is how do I handle users and access tokens for them and verify them through drf. I could totally depend on drf for OAuth and create users using http request in front end using OAuth toolikt for Django but is there a way that I handle incoming auth tokens in front end and verify them in drf so as to register them in backend.
#api_view(http_method_names=['POST'])
#permission_classes([AllowAny])
#psa()
def exchange_token(request, backend):
serializer = SocialSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
# This is the key line of code: with the #psa() decorator above,
# it engages the PSA machinery to perform whatever social authentication
# steps are configured in your SOCIAL_AUTH_PIPELINE. At the end, it either
# hands you a populated User model of whatever type you've configured in
# your project, or None.
user = request.backend.do_auth(serializer.validated_data['access_token'])
if user:
# if using some other token back-end than DRF's built-in TokenAuthentication,
# you'll need to customize this to get an appropriate token object
token, _ = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
else:
return Response(
{'errors': {'token': 'Invalid token'}},
status=status.HTTP_400_BAD_REQUEST,
)
There’s just a little more that needs to go in your settings (full code), and then you’re all set:
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
for key in ['GOOGLE_OAUTH2_KEY',
'GOOGLE_OAUTH2_SECRET',
'FACEBOOK_KEY',
'FACEBOOK_SECRET']:
# Use exec instead of eval here because we're not just trying to evaluate a dynamic value here;
# we're setting a module attribute whose name varies.
exec("SOCIAL_AUTH_{key} = os.environ.get('{key}')".format(key=key))
SOCIAL_AUTH_PIPELINE = (
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.social_auth.associate_by_email',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
)
Add a mapping to this function in your urls.py, and you’re all set!

Rails - How to authorise user with third party api

I'm setting up some authentication in my rails application. Only thing is I want to log in a user based on their credentials with another API.
The application will have to send a POST request with their username and password in the body to the API and if the request is successful then the user authorised.
I'm having trouble trying to do this with devise, I'm just looking for tips you guys have in order to implement this.
Thanks!
Devise allows you to define custom strategies for authentication. You can therefore create a new strategy to handle it. Database Authentication is one of the strategy already defined at Devise. You can check the source here
A rough idea of your strategy could like this.
Create a file at config/initializers/external_authenticatable.rb and define the strategy
require 'devise/strategies/database_authenticatable'
module Devise
module Strategies
class ExternalAuthenticatable < DatabaseAuthenticatable
def authenticate!
resource = password.present? && mapping.to.find_for_database_authentication(authentication_hash)
if validate(resource){ valid_credentials?(resource) }
remember_me(resource)
resource.after_database_authentication
success!(resource)
end
fail(:not_found_in_database) unless resource
end
def valid_credentials?(resource)
request_params = { email: resource.email, password: password }
# Make your post request here and return true false using authentication_hash
end
end
end
end
Now we need to inform devise that we want to use this strategy first before any other defaults. This can be done by editing /config/initializers/devise.rb
config.warden do |manager|
manager.strategies.add(:external, Devise::Strategies::ExternalAuthenticatable)
manager.default_strategies(:scope => :user).unshift :external
end
Restart your Rails application and you are done.

Locust tasks that follow redirects

I'm load testing a local API that will redirect a user based on a few conditions. Locust is not redirecting the simulated users hitting the end points and I know this because the app logs all redirects. If I manually hit the end points using curl, I can see the status is 302 and the Location header is set.
According to the embedded clients.HttpSession.request object, the allow_redirects option is set to True by default.
Any ideas?
We use redirection in our locust test, especially during the login phase. The redirects are handled for us without a hitch. Print the status_code of the response that you get back. Is it 200, 3xx or something worse?
Another suggestion: Don't throw your entire testing workflow into the locust file. That makes it too difficult to debug problems. Instead, create a standalone python script that uses the python requests library directly to simulate your workflow. Iron out any kinks, like redirection problems, in that simple, non-locust test script. Once you have that working, extract what you did into a file or class and have the locust task use the class.
Here is an example of what I mean. FooApplication does the real work. He is consumed by the locust file and a simple test script.
foo_app.py
class FooApplication():
def __init__(self, client):
self.client = client
self.is_logged_in = False
def login(self):
self.client.cookies.clear()
self.is_logged_in = False
name = '/login'
response = self.client.post('/login', {
'user': 'testuser',
'password': '12345'
}, allow_redirects=True, name=name)
if not response.ok:
self.log_failure('Login failed', name, response)
def load_foo(self):
name = '/foo'
response = self.client.get('/foo', name=name)
if not response.ok:
self.log_failure('Foo request failed ', name, response)
def log_failure(self, message, name, response):
pass # add some logging
foo_test_client.py
# Use this test file to iron out kinks in your request workflow
import requests
from locust.clients import HttpSession
from foo_app import FooApplication
client = HttpSession('http://dev.foo.com')
app = FooApplication(client)
app.login()
app.load_foo()
locustfile.py
from foo_app import FooApplication
class FooTaskSet(TaskSet):
def on_start(self):
self.foo = FooApplication(self.client)
#task(1)
def login(self):
if not self.foo.is_logged_in:
self.foo.login()
#task(5) # 5x more likely to load a foo vs logging in again
def load_foo(self):
if self.foo.is_logged_in:
self.load_foo()
else:
self.login()
Since Locust uses the Requests HTTP library for Python, you might find your answer there.
The Response object can be used to evaluate if a redirect has happened and what the history of redirects contains.
is_redirect:
True if this Response is a well-formed HTTP redirect that could have been
processed automatically (by Session.resolve_redirects).
There might be an indication that the redirect is not well-formed.

What's the alternative of before_filter from Rails in Phoenix/Elixir?

In my Phoenix application, I have a pipe and scope "api"
pipeline :api do
plug(:accepts, ["json"])
end
scope "/api" .....
# .....
end
How can I protect it by an API Key which is passed through a special header? That is, I'd like something like this:
defmodule MyApp.MyController do
use MyApp.Web, :controller
:before_filter :authenticate_user_by_api_key!
def authenticate_user_by_api_key!(conn, params) do
# check if a header exists and key is valid
end
end
I'm planning on validating a header. How can I do that without relying on any third-party libraries?
Also. If I wanted to use a module instead of a single function, how would I do that?
Local Plug
If it's a local method, you can simply use the plug construct within the controller.
defmodule MyApp.MyController do
use MyApp.Web, :controller
plug :authenticate_user_by_api_key!
defp authenticate_user_by_api_key!(conn, params) do
# Authenticate or something
end
end
See this answer and read more about Plugs here.
Module Plug
If you'd like to call the function from a Module, your module must export the init/1 and call/2 methods:
defmodule MyApp.Plugs.Authentication do
import Plug.Conn
def init(default), do: default
def call(conn, default) do
# Check header for API Key
end
end
And use it like this in your controller:
defmodule MyApp.MyController do
use MyApp.Web, :controller
plug MyApp.Plugs.Authentication
# Controller Methods
end
Read the Phoenix Guide on Module Plugs for more details.

Testing Phoenix channels (for chat) over iex

I'm using the standard (canonical?) Phoenix chat example to build something. But because I'm going to be handling the back-end only, I don't want to go through the trouble of wrestling with JavaScript on the client side. I'd like to test my room creation and broadcast over 3-4 terminal sessions, which will act as users.
So here's what I tried straightaway:
iex(2)> Rtc.RoomChannel.join("rooms:gossip", "hey!", {})
{:ok, {}}
Hmmm, that's weird. I was supposed to get back a socket. Wait a minute! Silly me, I just passed an empty tuple instead of a socket, and got it back. Which means I just need to pass a valid socket as the third parameter. Cool! . . .
But how to get a socket? I then remembered that we have something called the user_socket in the channels directory, so I tried something like this:
iex(5)> h MyApp.UserSocket.connect
#callback connect(params :: map(), Phoenix.Socket.t()) :: {:ok, Phoenix.Socket.t()} | :error
Aha! Now I know how to create sockets. So let's create one:
iex(6)> MyApp.UserSocket.connect(%{}, Phoenix.Socket.t())
** (UndefinedFunctionError) undefined function Phoenix.Socket.t/0
(phoenix) Phoenix.Socket.t()
And indeed, there's no t() function in the Phoenix.Socket module.
Where have I gone wrong? Is it even possible to create sockets like this, or am I doomed to have a JavaScript client?
You should take a look at Phoenix.Socket module.This is how connect/2 callback is defined:
...
alias Phoenix.Socket
#callback connect(params :: map, Socket.t) :: {:ok, Socket.t} | :error
...
#type t :: %Socket{id: nil,
assigns: %{},
channel: atom,
channel_pid: pid,
endpoint: atom,
handler: atom,
joined: boolean,
pubsub_server: atom,
ref: term,
topic: String.t,
transport: atom,
transport_name: atom,
serializer: atom,
transport_pid: pid}
Typespecs
Remote types
Any module is also able to define its own types and the modules in
Elixir are no exception. For example, the Range module defines a t
type that represents a range: this type can be referred to as Range.t.
In a similar fashion, a string is String.t, any enumerable can be
Enum.t, and so on.