GithubHelp home page GithubHelp logo

dwyl / auth_plug Goto Github PK

View Code? Open in Web Editor NEW
29.0 29.0 1.0 377 KB

🟢 auth_plug lets you seamlessly add authentication to your Phoenix App with ONE Environment Variable™ in less than 2 minutes! 🚀

Home Page: https://auth-plug.fly.dev/

License: GNU General Public License v2.0

Elixir 100.00%
auth0 authentication elixir jwt phoenix phoenix-framework

auth_plug's People

Contributors

dependabot[bot] avatar luchoturtle avatar nelsonic avatar simonlab avatar th0mas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

th0mas

auth_plug's Issues

Basic Example of using OAuth

HTTP/1.1 200 OK
x-li-format:
json
X-LI-UUID:
4WibPdX7HBTAS06wwyoAAA==
Vary:
*
Transfer-Encoding:
chunked
Date:
Sat, 05 Dec 2015 10:07:53 GMT
X-Li-Fabric:
prod-ltx1
x-li-request-id:
0X8LRK962F
Set-Cookie:
lidc="b=TB15:g=271:u=201:i=1449310073:t=1449339790:s=AQH2IDHD--6fvdaMBHAHwFDDd_IHoD5l"; Expires=Sat, 05 Dec 2015 18:23:10 GMT; domain=.linkedin.com; Path=/
X-Li-Pop:
prod-lva1
Connection:
keep-alive
Content-Type:
application/json;charset=UTF-8
Server:
Apache-Coyote/1.1

Which returns:

{
  "firstName": "Nelson Kenneth",
  "headline": "Code Whisperer",
  "id": "Oa-Qj8_ko0",
  "lastName": "Correia",
  "siteStandardProfileRequest":  {
    "url": "https://www.linkedin.com/profile/view?id=AAoAAAPD04MB-uOjoxO28Ux-glJAudlTe3ytEiA&authType=name&authToken=le11&trk=api*a3227641*s3301901*"
  }
}

is_nil never works in get_token_from_header

The function get_token_from_header contains the if is_nil(value) do condition which is never true because an empty string can't be nil

auth_plug/lib/token.ex

Lines 208 to 224 in 3c7ff1f

defp get_token_from_header({"authorization", value}) do
value = String.replace(value, "Bearer", "") |> String.trim()
# fast check for JWT format validity before slower verify:
# Does this ever eval to true?
if is_nil(value) do
nil
else
case Enum.count(String.split(value, ".")) == 3 do
false ->
nil
# appears to be valid JWT proceed to verifying it
true ->
value
end
end
end

Check for/ DWYL_API_KEY before AUTH_API_KEY

At present we are using SECRET_KEY_BASE as our single environment variable:

export SECRET_KEY_BASE=2PzB7PPnpuLsbWmWtXpGyI+kfSQSQ1zUW2Atz/+8PdZuSEJzHgzGnJWV35nTKRwx

Which is then used in:
@secret System.get_env("SECRET_KEY_BASE")

As part of our Quest to only have a Single Environment Variable to run the App on localhost dwyl/auth#42 we need to use that variable in our Token module.

Todo

  • Check for DWYL_API_KEY as the @secret in Token module
  • Test for it! 💯
  • Split the DWYL_API_KEY on / and
    • use the second half (corresponding to the client_secret) to verify the JWT
    • If JWT verification fails, send the first half of the DWYL_API_KEY (corresponding to the client_id) as the client_id in the URL query param so that the Auth App can verify it.

Update Instructions for Getting AUTH_API_KEY

In light of the changes made in dwyl/auth#85, specifically the creation of Apps,
we need to update the instructions for creating an AUTH_API_KEY.

Todo

  • Create a GIF of the new steps.

    Note: it's still the same number of steps and fields to complete and hopefully clearer.

  • Summarise the steps verbally for anyone who could be confused by the GIF
  • Link to the home page of the auth app: https://dwylauth.herokuapp.com (instead of deep-linking to /apps/new) as the new workflow is quite clear and it's basically impossible to get it wrong.
  • Create PR with update
  • Assign PR for review

For reference, these are the current instructions with an illustrative (animated) GIF:


6. Get and Set the AUTH_API_KEY Environment Variable

Visit: https://dwylauth.herokuapp.com/settings/apikeys/new
and create a New API Key.

auth_plug_example_setup

Save the key as an environment variable named AUTH_API_KEY.
Remember to export the environment variable
or add it to an .env file which should be in your .gitignore file.

Logout?

According to Suhail Doshi (co-founder of MixPanel), they did not have Logout functionality for the first 9 months because nobody asked for it. People who didn't understand their product simply closed their browser and didn't return. That's the biggest problem in building an App.
image
https://youtu.be/MABmQhOlmJA

image
If people aren't using the App, then Logout is the least of their concerns.

The moment a person using our product requests the ability to logout from the app (e.g because they use a shared computer) we will add an interface AuthPlug.logout/1 where the single argument is the conn and it just returns the conn with the session deleted.
We then need to figure out how to avoid session replay, because technically the JWT has not been invalidated it's just been deleted from the session cookies. 💭

2021 Update

I still don't think that logout is something we (or the people using our apps) need.
But just for testing purposes I think it will be easier/faster to have the functionality.

@iteles / @SimonLab what do you think?

Todo

  • Add AuthPlug.logout/1 function that deletes the session.
  • Ideally we would also send this logout request back to the Auth app to prevent session fixation/hijacking ... but since the sessions are stateless we would need to implement a whole other set of callback functions for checking session expiry/validity. 💭 GOTO Session Management: dwyl/auth#30

Cookie “SameSite” attribute set to “None” (soon to be rejected)

Just saw this warning in Firefox:

Cookie “_app_key” will be soon rejected because it has the “SameSite” attribute set to “None” or an invalid value, without the “secure” attribute. To know more about the “SameSite“ attribute, read https://developer.mozilla.org/docs/Web/HTTP/Headers/Set-Cookie/SameSite

key `:loggedin` not found in `assigns`

** (exit) an exception was raised:
    ** (KeyError) key :loggedin not found in: %{conn: %Plug.Conn{adapter: {Plug.Cowboy.Conn, :...}, assigns: %{layout: {ChatWeb.LayoutView, "app.html"}}

Todo

  • assign the value of false to :loggedin in auth_plug by default so that our templates work in consuming apps.
  • publish new version

How to avoid raising an exception when JWT validation fails?

Stack trace:

[info] Sent 500 in 53ms
[error] #PID<0.572.0> running AppWeb.Endpoint (connection #PID<0.571.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /admin
** (exit) an exception was raised:
    ** (ArgumentError) argument error
        :erlang.atom_to_binary([message: "Invalid token", claim: "exp", claim_val: 1587485301], :utf8)
        (auth_plug 0.2.0) lib/auth_plug.ex:97: AuthPlug.validate_token/3
        (app 0.1.0) AppWeb.Router.auth/2
        (app 0.1.0) lib/app_web/router.ex:1: AppWeb.Router.__pipe_through1__/1
        (phoenix 1.4.16) lib/phoenix/router.ex:283: Phoenix.Router.__call__/2
        (app 0.1.0) lib/app_web/endpoint.ex:1: AppWeb.Endpoint.plug_builder_call/2
        (app 0.1.0) lib/plug/debugger.ex:132: AppWeb.Endpoint."call (overridable 3)"/2
        (app 0.1.0) lib/app_web/endpoint.ex:1: AppWeb.Endpoint.call/2
        (phoenix 1.4.16) lib/phoenix/endpoint/cowboy2_handler.ex:42: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy 2.7.0) /Users/n/code/auth_plug_example/app/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy 2.7.0) /Users/n/code/auth_plug_example/app/deps/cowboy/src/cowboy_stream_h.erl:320: :cowboy_stream_h.execute/3
        (cowboy 2.7.0) /Users/n/code/auth_plug_example/app/deps/cowboy/src/cowboy_stream_h.erl:302: :cowboy_stream_h.request_process/3
        (stdlib 3.11.2) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Convert Person Struct to Map before Jason.encode

Apparently Jason.encode!/2 cannot accept a Struct. 🤦

  1) test google_handler/2 show welcome (state=nil) > handler/3 (AuthWeb.PageControllerTest)
     test/auth_web/controllers/page_controller_test.exs:30
     ** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for %{:__meta__ => #Ecto.Schema.Metadata<:loaded, "people">, :__struct__ => Auth.Person, :auth_provider => "google", :email => "[email protected]", :email_hash => "[email protected]", :familyName => "Correia", :givenName => "Nelson", :id => 2, :inserted_at => ~N[2020-04-28 14:56:26], :key_id => nil, :locale => "en", :password => nil, :password_hash => nil, :picture => "https://lh3.googleusercontent.com/a-/AAuE7mApnYb260YC1JY7a", :status => 1, :statuses => #Ecto.Association.NotLoaded<association :statuses is not loaded>, :tag => nil, :updated_at => ~N[2020-04-28 14:56:26], :username => nil, :username_hash => nil, "aud" => "Joken", "exp" => 1619622786, "iat" => 1588085786, "iss" => "Joken", "jti" => "2o5076u24l10g5vt5g0000b6", "nbf" => 1588085786} of type Auth.Person (a struct), Jason.Encoder protocol must always be explicitly implemented.

     If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

         @derive {Jason.Encoder, only: [....]}
         defstruct ...

     It is also possible to encode all fields, although this should be used carefully to avoid accidentally leaking private information when new fields are added:

         @derive Jason.Encoder
         defstruct ...

     Finally, if you don't own the struct you want to encode to JSON, you may use Protocol.derive/3 placed outside of any module:

         Protocol.derive(Jason.Encoder, NameOfTheStruct, only: [...])
         Protocol.derive(Jason.Encoder, NameOfTheStruct)
     . This protocol is implemented for the following type(s): Ecto.Association.NotLoaded, Ecto.Schema.Metadata, Date, BitString, Jason.Fragment, Any, Map, NaiveDateTime, List, Integer, Time, DateTime, Decimal, Atom, Float
     code: conn = get(conn, "/auth/google/callback", %{code: "234", state: nil})
     stacktrace:
       (jason 1.2.0) lib/jason.ex:150: Jason.encode!/2
       (jose 1.10.1) src/jwt/jose_jwt.erl:89: :jose_jwt.to_binary/1
       (jose 1.10.1) src/jwt/jose_jwt.erl:172: :jose_jwt.sign/3
       (joken 2.2.0) lib/joken/signer.ex:119: Joken.Signer.sign/2
       (joken 2.2.0) lib/joken.ex:365: Joken.encode_and_sign/3
       (auth_plug 0.12.0) lib/token.ex:70: AuthPlug.Token.generate_jwt!/2
       (auth_plug 0.12.0) lib/auth_plug.ex:114: AuthPlug.create_jwt_session/2

image

Thankfully we know that it's fairly straightforward to convert a Struct to a Map:
https://stackoverflow.com/questions/36512627/elixir-convert-struct-to-map

Project doesn't compile without an API Key set

Mix fails to compile the project without an API key set due to the module attributes used in the Demo application.

Is it possible to set these at runtime to avoid this and allow for some nicer error messages?

Update Dependencies

Failed to use "plug" (versions 1.10.2 and 1.10.3) because
  auth_plug (version 1.2.3) requires ~> 1.10.4
  phoenix (version 1.6.0) requires ~> 1.10
  phoenix_ecto (version 4.4.0) requires ~> 1.9
  phoenix_html (versions 3.0.0 to 3.0.4) requires ~> 1.5
  ping (version 1.0.1) requires ~> 1.10.1
  plug_cowboy (version 2.5.2) requires ~> 1.7


Failed to use "plug" (versions 1.11.0 to 1.12.1) because
  auth_plug (version 1.2.3) requires ~> 1.10.4
  phoenix (version 1.6.0) requires ~> 1.10
  phoenix_ecto (version 4.4.0) requires ~> 1.9
  phoenix_html (versions 3.0.0 to 3.0.4) requires ~> 1.5
  plug_cowboy (version 2.5.2) requires ~> 1.7

Todo

  • Update all deps
  • Create PR
  • Publish new version to Hex.pm

How to pass parameters to a plug?

How can we pass the parameter of the /auth URL to the auth_plug so that it redirects to the desired endpoint if the there is no valid JWT?

Using `authOptional` in `:api` pipelines

I'm trying to use this plug in an :api pipeline.

Having read the documentation, doing

pipeline :authoptional, do: plug(AuthPlugOptional, %{})

should be enough to get up and going.
Adding this pipeline to the scope should be fairly simple, like so:

  scope "/api", AppWeb do
    pipe_through [:api, :authOptional]

    resources "/items", ItemController, only: [:create, :update]
  end

However, I keep stumbling upon this error when calling the app anonymously.

session not fetched, call fetch_session/2

This error doesn't occur when I add a Bearer Token, for example.

This error occurs in auth_plug and I've located where. The error is in get_jwt, when calling get_session..

It's weird, this error shouldn't be happening according to the Conn.Plug's source code, since the :jwt atom is being passed.

I'm aware this could easily be surpassed by just creating the authOptional pipeline with a plug:fetch_session, like so:

  pipeline :authOptional do
    plug :fetch_session
    plug(AuthPlugOptional)
  end

However, I feel like auth_plug should gracefully handle these scenarios.

Should a guard be added in get_jwt? Or is it the user's responsibility to fetch_session in their :authOptional pipelines?

Optional Auth

There are often cases in our App(s) where we want to display personalised content
if the person is logged in or display generic info if they are not logged in.

At present we only have the ability to always require authentication for a given route.
What we want is optional auth which initialises the session if the cookie is set but ignores it if not.

Todo

  • Create helper function that merely checks the session/cookie for JWT
    • and ensures that the session data is assigned on the conn so that any routes/templates that can benefit from having conn.assigns.person defined will have it.
  • Document how to use this "optional" auth in a router.ex for a Phoenix App.

Relevant to: dwyl/auth#69

Should we make the DWYL_API_KEY more generic e.g AUTH_API_KEY?

I quite like the idea of having DWYL in the name of the environment variable for complete clarity.
But if we want to make our code more generic so that it's useful to others,
we could consider changing the name of the environment variable to AUTH_API_KEY.

The biggest advantage of making code generic and reusable by others is that it encourages shared ownership and thus maintenance and enhancement of the code. We have seen that in many of our Open Source projects e.g: hapi-auth-jwt2 which has 29k weekly downloads and 53 contributors!
We built it for ourselves and used it on several Node.js (Hapi) projects e.g: the Science Museum https://github.com/TheScienceMuseum/collectionsonline/blob/8fc56a46f663ed71392ebbe7e3a36d31732f50bd/package.json#L31
The extra effort documenting and testing it means that it reduces duplication of effort for other people and in the case of the most recent major update dwyl/hapi-auth-jwt2#249 a member of the community did the vast majority of the work. ❤️ (that's when Open Source pays off... when you get a "free" upgrade.)

With this mind, how do we feel about:

@secret System.get_env("DWYL_API_KEY") || System.get_env("AUTH_API_KEY")

That way we can keep our DWYL_.. key for clarity in our own apps,
and make it generic for other people to use. 💭

Redirect (Referer) Should be a Fully Qualified URL

At present I am redirecting from localhost:4000/admin to the Heroku Demo app:
image

But once the Auth is successful, the redirect does not work because I'm sending a relative url.
It should be an absolute URL so that we can have our auth app on a separate subdomain.

Todo

  • ensure that referer is a fully qualified URL e.g: https://dwyl.com/admin not /admin

Chore: Strip `JWT` from URL once authenticated

As noted in dwyl/auth#268 the JWT for a successfully authentication session remains in the URL:
image

This is undesirable because if there was a malicious <link> on the page
or someone loaded an <img> on the page that made an outbound HTTP Request,
the JWT would be in the referrer header of the request
and thus the session could be compromised.
i.e. a malicious actor could just extract the JWT from their logs
and replay it to gain access to everything the person has saved in the dwyl App.

Note: this is not an "active exploit". We are still testing our MVP.
Nobody has stored any personal/private/important data in the MVP
and there has not been any indication of anyone malicious attempting to "steal" a JWT.
I am opening this issue proactively to resolve this before it becomes an exploit.

Todo

  • Strip jwt from URL once the session has been established.

Note: this will be rolled into the V2 update "Coming Soon" ... 🔜
So please ignore it until then. 👌

SETUP MODE: Don't throw Error when `AUTH_API_KEY` is not set

At present if the AUTH_API_KEY is not set as an environment variable, then auth_plug will throw an error.
e.g:

== Compilation error in file lib/auth_web/router.ex ==
** (RuntimeError) No AUTH_API_KEY set, find out how at: https://git.io/JJ6sS
    (auth_plug 1.4.7) lib/helpers.ex:40: AuthPlug.Helpers.check_environment_vars/0
    (auth_plug 1.4.7) lib/auth_plug.ex:33: AuthPlug.init/1
    (plug 1.12.1) lib/plug/builder.ex:318: Plug.Builder.init_module_plug/4
    (plug 1.12.1) lib/plug/builder.ex:302: anonymous fn/5 in Plug.Builder.compile/3
    (elixir 1.12.3) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (plug 1.12.1) lib/plug/builder.ex:300: Plug.Builder.compile/3
    lib/auth_web/router.ex:42: (module)

It seemed like a good idea at the time we were creating this, because the only time you would see this would be during setup of an app that uses auth_plug ... and for that the instructions were pretty clear: https://git.io/JJ6sS
However on further reflection, while the AUTH_API_KEY is not set it means that the App is in "Setup Mode" and shouldn't error. But should inform the person in their terminal and browser that the environment variable is missing.

Relates to: dwyl/auth#170 Circular Dependency on auth_plug in the auth App.

Can we rely on ID to stay constant?

auth_plug makes an id field available in the conn assigns. This seems to be set by the auth application which I haven't looked into much. Can this be relied on to uniquely identify a user?

If not, it's probably worth updating #22 to make this clear.

Create Session for Person (For Testing)

In testing it makes sense to have a function that creates a session for a given person.

Todo

  • create create_session/3 function that accepts a conn and person
    and returns the session on the conn with the session attached.
  • create create_session_mock/2 function that invokes setup_session/1 and create_session/3

Create Auth Plug and Publish Package to Hex.pm

One of the beauties of the Plug specification for composable web app modules
is that plugs can be used as "Middleware" similar to a Plugin or Hook.

Todo

Thankfully @SimonLab has already done some of the work for this:
https://github.com/dwyl/auth-mvp/blob/master/lib/plugs/authenticate_person.ex

We just need to add:

Make generate_jwt/2 available to sign payload with any key

At present we only have generate_jwt!/1 which takes a claims Map but uses a default signing key:

auth_plug/lib/token.ex

Lines 21 to 27 in 5936433

def generate_jwt!(claims) do
{:ok, token, _claims} =
token_config()
|> Joken.generate_and_sign(claims, @signer)
token
end

In order to allow us to sign JWTs with any key (so that each client has it's own key)
a DWYL_API_KEY dwyl/auth#42 we need the ability to supply the signing key as an argument.

Todo

Expose a the following functions that will allow us to supply any signing key:

  • generate_jwt!/2
  • verify_jwt/2
  • verify_jwt!/2

Make decoded JWT available on conn.assigns.person

At present we are assigning the verified and decode JWT to conn.assigns.decoded:

|> assign(:decoded, claims)

This made sense at the time we wrote it because the :decoded key is the decoded JWT. 💭
But in the context of an App using auth_plug I find myself writing conn.assigns.decoded.id a lot when I think it would be more intuitive to write conn.assigns.person.id ...

Given that nobody else is using auth_plug yet, now is the time to make this change.
I think it will make things a lot clearer for people in the future.

Todo

  • Rework create_session/3 to assign the decoded claims to conn.assigns.person

    auth_plug/lib/auth_plug.ex

    Lines 97 to 103 in 3bcdac1

    def create_session(conn, claims, jwt) do
    claims = AuthPlug.Helpers.strip_struct_metadata(claims)
    conn
    |> assign(:decoded, claims)
    |> assign(:person, jwt)
    |> put_session(:person, jwt)
    end
  • Assign the original JWT to conn.assigns.jwt i.e. assign(:jwt, jwt) instead of assign(:person, jwt)
  • Define the session as jwt instead of person i.e. put_session(:jwt, jwt)
  • Update the cond block to look for the jwt by the :jwt key instead of :person:
    jwt =
    cond do
    # First Check for JWT in URL Query String.
    # We want a *new* session to supercede any expired session,
    #  so the check for JWT *before* anything else.
    conn.query_string =~ "jwt" ->
    query = URI.decode_query(conn.query_string)
    Map.get(query, "jwt")
    # Check for JWT in Headers:
    Enum.count(get_req_header(conn, "authorization")) > 0 ->
    conn.req_headers
    |> List.keyfind("authorization", 0)
    |> get_token_from_header()
    #  Check for Person in Plug.Conn.assigns
    Map.has_key?(conn.assigns, :person) && not is_nil(conn.assigns.person) ->
    conn.assigns.person
    # Check for Session in Plug.Session:
    not is_nil(get_session(conn, :person)) ->
    get_session(conn, :person)
    # By default return nil so auth check fails
    true ->
    nil
    end
  • Update the tests.
  • Publish a new version with the breaking change. (We don't need a major bump as nobody is using the package yet so 1.1.0 is fine)

Reponse 200 on logout?

While using the logout function in phoenix-liveview-chat-example I noticed the function set the status code to 200 using the resp function:

def logout(conn) do
# https://stackoverflow.com/questions/42325996/delete-assigns
conn = update_in(conn.assigns, &Map.drop(&1, [:jwt, :person]))
conn
# see below. makes REST API req to auth_url/end_session
|> end_session()
# hexdocs.pm/plug/Plug.Conn.html#delete_session/2,
|> delete_session(:jwt)
# hexdocs.pm/plug/Plug.Conn.html#clear_session/1
|> clear_session()
#  stackoverflow.com/questions/30999176
|> configure_session(drop: true)
|> assign(:state, "logout")
|> resp(200, "logged out")
end

I'm currently using the logout function then redirecting to another page, however without using put_status(conn, 302) the redirection returns the default Phoenix redirection page because the status code is 200:

def logout(conn, _params) do
  conn
  |> AuthPlug.logout()
  |> put_status(302) # add staus code to 302
  |> redirect(to: "/")                                                                                                                                                               
end            

So I'm wondering if we can just remove the resp(conn, 200) line in the logout function and let the consumer apps reply what they want (200, redirection,...)?

Create `get_auth_url` public function

Having a public api to access the auth_url might be useful when the consumer application has only one optional auth route.
In this case we might want to create a link to allow user to login directly from this optional auth page.

For example at the moment in auth_plug_example to login on the optional page we are creating a link to the admin page:
see https://github.com/dwyl/auth_plug_example/blob/8cc2822f013361c48ee63c14c8cc7bcade4973ea/lib/app_web/templates/page/optional.html.eex#L12

try <a href="/admin">login</a>
  • Create the get_auth_url\2 function which can takes the connection conn and an optional string parameter to let know the auth app where to redirect to after authenticated

I wanted to use this type of function in dwyl/phoenix-liveview-chat-example#15

Docs: Add section on @dependabot environment variable access

As noted in dwyl/mvp#258 when @dependabot does not have access to AUTH_API_KEY,
the @dependabot PRs to update dependencies fail. 😞

Todo

  • Add a section to the README.md advising people how they can add a dependabot-accessible environment variable on GitHub so that deps update PRs pass. 👌

This should only take T10mins but our nearest label is T25m so that's what I've applied. ⏳
Not urgent but nice-to-have so that we can apply this knowledge to all of the repos/examples using auth_plug. 💭

Create Helper Function that Generates Valid JWT

Our auth_plug is working as desired and provides protection for our routes,
but testing routes protected by the plug is a little tedious; we can do better.

Todo

  • Create a helper function that generates a valid JWT with some valid-looking data
    so that requests to protected routes can be tested.

Update Logout HTTP Request METHOD from GET to POST

As discussed in the PR that added logout, we should replace the GET request with a POST.

Todo

  • Update method here in auth_plug
  • Update auth route to POST
  • Update tests auth to use POST request
  • Publish a new version to Hex.pm

Renew Session JWT if it is about to expire

If a Session JWT is expiring soon, renew it.
We might need to add a max_age prop to init(options) with a default of 2 weeks to ensure that this is customisable. But for now we will just renew it indefinitely as long as the current JWT has not expired.

Exporting env variable appears erronous

I'm following through using this plug and it appears to work correctly, thank you very much!

However, during my implementation, I only seemed to get it working when I was using https://auth.dwyl.com/ instead of the deployed Heroku app, as written in README.

Additionally, after creating my own app inside auth.dwyl.com, this plug only worked when I exported the variable WITHOUT the /auth.dwyl.com portion of the text for it to work.

Screenshot 2022-10-13 at 15 44 03

Otherwise, I got a :signature_error from this plug every time I tried to sign in with GitHub.

I can make a PR to fix the README file with these changes if you want to, just give me a heads-up if my walkthrough is a valid one.

Plug dependency might be a blocker when importing auth_plug in new projects

I was trying to use this plug in a fork repo of Phoenix Chat App and I can't seem to be able to run mix deps.get properly with the latest version (1.4.14).

The repo I'm trying to import this into has all its dependencies updated, which makes this situation awfully weird.

Failed to use "plug" (versions 1.10.2 to 1.13.3) because
  auth_plug (version 1.4.14) requires ~> 1.13.4
  phoenix (version 1.6.14) requires ~> 1.10
  phoenix_ecto (version 4.4.0) requires ~> 1.9
  phoenix_html (version 3.2.0) requires ~> 1.5
  plug_cowboy (version 2.5.2) requires ~> 1.7

Failed to use "plug" (versions 1.13.4 to 1.13.6) because
  auth_plug (version 1.4.14) requires ~> 1.13.4
  phoenix (version 1.6.14) requires ~> 1.10
  phoenix_ecto (version 4.4.0) requires ~> 1.9
  phoenix_html (version 3.2.0) requires ~> 1.5
  ping (version 1.1.0) requires ~> 1.12.1
  plug_cowboy (version 2.5.2) requires ~> 1.7


Failed to use "telemetry" (versions 0.4.0 and 0.4.1) because
  ecto_sql (version 3.9.0) requires ~> 0.4.0 or ~> 1.0
  phoenix (version 1.6.14) requires ~> 0.4 or ~> 1.0
  phoenix_live_view (version 0.18.2) requires ~> 0.4.2 or ~> 1.0
  plug (versions 1.10.0 and 1.10.1) requires ~> 0.4
  swoosh (version 1.8.1) requires ~> 0.4.2 or ~> 1.0
  telemetry_metrics (version 0.6.1) requires ~> 0.4 or ~> 1.0


Failed to use "telemetry" (versions 0.4.2 and 0.4.3) because
  ecto_sql (version 3.9.0) requires ~> 0.4.0 or ~> 1.0
  phoenix (version 1.6.14) requires ~> 0.4 or ~> 1.0
  phoenix_live_view (version 0.18.2) requires ~> 0.4.2 or ~> 1.0
  plug (versions 1.10.0 and 1.10.1) requires ~> 0.4
  swoosh (version 1.8.1) requires ~> 0.4.2 or ~> 1.0
  telemetry_metrics (version 0.6.1) requires ~> 0.4 or ~> 1.0
  telemetry_poller (version 1.0.0) requires ~> 1.0

The error seems to stem from the plug dependency this package is using. It's the latest version but apparently other packages that I have constraint the plug versions I have between 1.10.2 to 1.13.3.

If we downgrade the plug dependency on this package to 1.13.13 (instead of 1.13.14), it should work, right?

Fixing this will probably fix #75 .

Create Helper Method for adding `person` and `loggedin` to `socket` `assigns`

As noted in #84 (comment) we currently have to add the person and loggedin manually in LiveView Apps. This is obviously an undesirable step and creates duplicated (non-DRY) code in several of our example/tutorial projects e.g:

https://github.com/dwyl/mvp/blob/0270bb5b610496197a5d28dd87bbb7412745663e/lib/app_web/controllers/auth_controller.ex#L12-L15

Todo

  • Create a helper function that accepts the jwt and socket and returns the socket with the

i.e. executes the following:

def assign_jwt_to_socket(jwt, socket) do
    claims =
      jwt
      |> AuthPlug.Token.verify_jwt!()
      |> AuthPlug.Helpers.strip_struct_metadata()
      |> Useful.atomize_map_keys()

    socket =
      socket
      |> assign_new(:person, fn -> claims end)
      |> assign_new(:loggedin, fn -> true end)
end

That way in any LiveView App that uses auth_plug this is a 1-liner.

Edit:

Decided to alter the assign_jwt_to_socket/3 function signature to the following:

  def assign_jwt_to_socket(socket, assign_new, jwt) do
    claims =
      jwt
      |> AuthPlug.Token.verify_jwt!()
      |> AuthPlug.Helpers.strip_struct_metadata()
      |> Useful.atomize_map_keys()

    socket =
      socket
      # Pass function by reference in Elixir:
      # stackoverflow.com/a/22562288/1148249
      |> assign_new.(:person, fn -> claims end)
      |> assign_new.(:loggedin, fn -> true end)

    socket
  end

Having socket as the first argument ensures that it is "chainable" e.g:

socket = socket
  |> AuthPlug.assign_jwt_to_socket(&Phoenix.LiveView.assign_new/3, jwt)
  |> assign_new(:another_param, fn -> true end)

Check for `end_session` errors

At the moment the end_session function called in the logout function always returns 200 status code:

auth_plug/lib/auth_plug.ex

Lines 132 to 133 in 9a52b82

conn
|> resp(200, response.message)

However the

  • get request could return an error:

    auth_plug/lib/auth_plug.ex

    Lines 128 to 129 in 9a52b82

    "#{auth_url}/end_session/#{client_id}/#{claims.sid}/"
    |> @httpoison.get()

  • the parse_body_response could also fail if the body doesn't exists or if Jason.decode fails:

    auth_plug/lib/auth_plug.ex

    Lines 103 to 108 in 9a52b82

    defp parse_body_response({:ok, response}) do
    body = Map.get(response, :body)
    {:ok, str_key_map} = Jason.decode(body)
    {:ok, Useful.atomize_map_keys(str_key_map)}
    end

We can add checks to make sure that all these possible errors are managed on logout

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.