{@thread.name}

simpers
2023-06-02

simpers:

As I’m exploring and trying to setup AshAuthentication I ended up using use AshAuthentication.Plug for a plug, thinking this would provide the same set of routes as the Phoenix view version would, but it doesn’t seem like it. I’m not even sure, as the docs aren’t clear on this and I’m at this point digging in the code itself.

Do I have to implement my own plug and controller(s) for this? I have an Angular frontend, and will maybe consume the same API from native apps later on.

And also, does this mean that some features aren’t available unless I use Phoenix?

zachdaniel:

You should be able to use ash_authentication_phoenix even if not building a UI.

simpers:

So I’ll just not pipe it through a :browser pipeline and instead my :api one, and it should work?

zachdaniel:

Honestly not too sure but I know people have set it up for APIs plenty of times before

zachdaniel:

Actually, maybe not maybe people have been rolling their own sign in/\sign out endpoints

simpers:

Reading the docs for the Phoenix setup it does include the :load_from_bearer function-plug in the example, so I assume it should be possible? I just reacted to the part where the routes were only included under a scope with :browser , which I do not have at all.

simpers:

Struggling to include the ~p sigil from Phoenix.VerifiedRoutes that is used in the AuthController example of the code in the docs. I could just delete it, but it bugs me why I can’t import it 😮

frankdugan3:

Assuming you have the generated function in your MyAppWeb file:

def verified_routes do
  quote do
   use Phoenix.VerifiedRoutes,
     endpoint: MyAppWeb.Endpoint,
     router: MyAppWeb.Router,
     statics: MyAppWeb.static_paths()
  end
end

You would enable verified routes in your module with:

use MyAppWeb, :verified_routes

frankdugan3:

Alternatively, if you are already doing this:

use MyAppWeb, :view

you might consider just adding:

unquote(verified_routes())

to the view macro.

simpers:

This is a controller, as per the example docs of AuthController . I did that, but optionally. Meaning I implemented a way to not get that. And as far as I can see it should have worked as intended, but it still complains unless I inline it in the controller.

simpers:

I can post my version of it later, but now I’m heading toward a train 🙂

_ahey:

I have an API only ash project successfully integrated with ash_authentication_phoenix . Perhaps not 100% relevant to your situation since I have a graphql api via ash_graphql .

In the router’s api pipeline I use plug(:load_from_bearer) , followed by my own plug that takes the actor from the assigns, does some processing and then calls Ash.PlugHelpers.set_actor .

Finally I have plug(AshGraphql.Plug) which makes the actor available to ash_graphql .

Following that I was able to use some of the built-in ash_authentication actions on my user object, while I had to wrap some of them in my own actions to customise or make more compatible with my API design. Some actions such as sign_out I had to implement myself.

simpers:

I also use GraphQL through AshGraphql

simpers:

At the time when I implemented my GraphQL stuff I had to use my own plugs, I think? <:thinkies:915154230078222336> Or maybe I just missed that those helper functions and plugs existed haha.

simpers:

This was my attempt at getting the option to include verified_routes:

  def controller(opts \\ []) do
    IO.inspect opts, pretty: true
    caller = Keyword.get(opts, :caller, nil)
    verified_routes? = Keyword.get(opts, :verified_routes, false)

    quote do
      use Phoenix.Controller, namespace: LpeWeb

      if unquote(verified_routes?) do
        IO.puts("Adding verified routes to: #{unquote(caller)}")
        unquote(verified_routes(caller: caller))
      end

      import Plug.Conn
      import LpeWeb.Gettext
      alias LpeWeb.Router.Helpers, as: Routes
    end
  end
  
  ...
  
  defp verified_routes(caller: caller) do
    IO.puts "using verified_routes #{inspect caller, pretty: true}"
    quote do
      use Phoenix.VerifiedRoutes,
        endpoint: LpeWeb.Endpoint,
        router: LpeWeb.Router,
        statics: ~w()
    end
  end

Ignore any IO calls as they wouldn’t be there normally. I just tried to debug and see what was going on as it seemingly didn’t work unless I inlined it into the controller itself.

simpers:

My problem is that I can’t figure out what I am supposed to POST to the endpoint for password registration:

↪ mix phx.routes
Compiling 1 file (.ex)
                auth_path  GET   /a/sign-out                               LpeWeb.AuthController :sign_out
                auth_path  *     /a/auth/user/password/sign_in_with_token  LpeWeb.AuthController {:user, :password, :sign_in_with_token}
                auth_path  *     /a/auth/user/password/register            LpeWeb.AuthController {:user, :password, :register}
                auth_path  *     /a/auth/user/password/sign_in             LpeWeb.AuthController {:user, :password, :sign_in}
                auth_path  *     /a/auth/user/password/reset_request       LpeWeb.AuthController {:user, :password, :reset_request}
                auth_path  *     /a/auth/user/password/reset               LpeWeb.AuthController {:user, :password, :reset}
                auth_path  *     /a/auth/user/magic_link/request           LpeWeb.AuthController {:user, :magic_link, :request}
                auth_path  *     /a/auth/user/magic_link                   LpeWeb.AuthController {:user, :magic_link, :sign_in}
                           *     /api/gql                                  Absinthe.Plug [schema: Lpe.Graphql.Schema, document_providers: [LpeWeb.Apq.DocumentProvider, Absinthe.Plug.DocumentProvider.Default]]
                           *     /api/playground                           Absinthe.Plug.GraphiQL [schema: Lpe.Graphql.Schema, interface: :playground]
...

I’m getting a 401 when trying to register, as it if wants me to be authenticated to begin with before even registering. Seems odd and incorrect. My pipelines in the router for these endpoints doesn’t require it and it is the AuthController’s failure/3 callback that reports this.

_ahey:

I chose a different design, where I use GQL for all of my auth. I have mutations for sign in, register and sign out etc so I never had to work out how to set up routes, other then my single /gql api endpoint, which was already working.

simpers:

Yeah, that’s what I was doing before I found out about AshAuthentication . Had hoped to just set this up and then not think about auth for a while 🥲 I’m so early in my MVP that I don’t want to think so much about the boring stuff

jharton:

Hi <@266991556576280588> if you’re just doing password authentication you can submit a JSON payload that looks like

%{
  resource_subject_name => %{
    identity_field => "my identity",
    password_field => "my password",
    password_confirmation_field => "my password}
}

straight to the generated sign-in route (you don’t need to send the confirmation field if confirmation is not enabled on your strategy). You will likely need to modify your auth controller/plug to return a JSON response rather than HTML. The other alternative which <@717986162282725387> suggested (and what I chose in another project recently) was to expose the sign in action to graphql and have the clients access every resource via the me graph node (ie me { posts { name } } gives me a list of all my related posts but returns null if there is no current user).

actions do
  read :current_user do
    get? true
    manual fn 
      %{resource: resource}, _, %{actor: actor} when is_struct(actor, resource) ->
        {:ok, [actor]}
      _, _, _ -> {:ok, []}
  end
end

graphql do
  type :user

  queries do
    get :me, :current_user do
      identity false
    end

    get :sign_in, :sign_in_with_password do
      type_name :user_with_token
      identity false
    end
  end
end

And have the GraphQL client take the token out of the returned payload and use it for auth. My router is set up as so:

pipeline :api do
  plug :accepts, ["json"]
  plug :load_from_bearer
  plug :set_actor, :user
end

scope "/" do
  pipe_through [:api, AshGraphql.Plug]

  forward "/gql", Absinthe.Plug, schema: MyApp.Schema
  forward "/playground", Absinthe.Plug.GraphiQL, schema: MyApp.Schema, interface: :playground
end

jharton:

Things get more complicated if you want to do OAuth - you will need a browser based flow for that.

simpers:

I want OAuth and other things, such as magic links and so on. And the confirmation was to confirm the email upon registration. I’m not at home at the moment so I’ll get back to you about the details tomorrow 🙂

franckstifler:

Hi, What did you fianlly go with <@266991556576280588>. I have the same issue. I can’t figure out how to add registration/sign-in/resets for api’s endpoints

simpers:

I’m still sitting here looking at the router. It was a national holiday in Sweden yesterday and today I’ve been off work so I haven’t been by the computer much for two days.

The problem to me seems to be that this is meant for an OAuth2 explicit flow (not sure of the exact definition of implicit vs explicit right now tbh, it’s been a while since I did this sort of thing). Though there are things in AshAuthentication to finish the remaining pieces on your own, I feel like there are some gaps in either the docs or the features to make this an explicit flow, which is what I needed. Not out of the box anyway. My frontend is Angular and not LiveView.

simpers:

I am not sure how I will solve it yet as I wanted to get the other features too, so I could hook up GitHub, Google, Apple ID, e.t.c..

jharton:

Happy to advise. If you implement a custom strategy you get very fine grained control over the whole process.

simpers:

Is custom necessary here because the existing ones don’t have what I suspected and described above?

I think I will be putting advanced auth on hold until my MVP takes a bit of shape first, and then come back to it. But it would be nice to contribute upstream if it would be possible/desired

jharton:

Im not sure because I’ve never tried to do oauth without a browser-based flow and I don’t know how that works. Re custom strategy - if you can’t get what you need from the built in strategies then this is the go to method to add your own.

zachdaniel:

Doesn’t oauth make no sense without a browser?

zachdaniel:

It requires the redirect to consent screens. Pretty sure it’s literally(or at least practically) impossible.

simpers:

The flow does not require a browser, as far as I’m aware. There is a difference in the flow, though. I’ve had to deal with Angular for a while and since it often relies on JWT rather than cookies, the flow is similar to that any native app. It’s just that the flow of credentials is a little different. Thus explicit vs implicit flow. Implicit is what this is, I think .

simpers:

Though I have always struggled to grasp the whole notion of this, and it was back in 2018 (gosh, time flies) when I researched this to implement some SSO stuff for some frontend(s).

simpers:

For example, I think when I fiddled with Angular + Facebook Auth/data I had to get the token through some pop-up, and then pass it along to my server so the server could store it and ack to Facebook that it received it, or something like that? It’s like it goes in a circle rather than the server always being in the middle.

zachdaniel:

lets back up. You have an api. You want people to be able to authenticate to use it, right?

simpers:

Yup!

zachdaniel:

sorry, got alot going on today, 😆

zachdaniel:

So when people authenticate to use your api, who are they?

zachdaniel:

Okay, nvm I think I see what you’re talking about

zachdaniel:

is this a way for people to give you some kind of token to access their information in some specific service?

zachdaniel:

what information are you expecting they would put in to your api to authenticate, for example

simpers:

It is for for both auth and accessing others data (calendars in this case)

simpers:

So sign-up can be with either pass/email as per the usual, but you should with time be able to sign-up with GitHub, Twitter, Google, AppleID, Discord whatever I might decide to add with time.

simpers:

And link calendars from say Google and Apple

zachdaniel:

but when people sign up that way, they’ll sign up in a browser, right?

simpers:

It should not be dependent on that

zachdaniel:

why not?

simpers:

If it’s a native app, it’ll use native APIs to accomplish this

zachdaniel:

native apps use oauth all the time, but typically they pop up a little browser window

simpers:

Yes, but the UI isn’t necessarily mine in these cases

simpers:

For Android the UI is Google’s

zachdaniel:

🤔 what does your app do?

simpers:

Not much yet haha. Still in MVP. But the idea is to gather a user’s calendars (work, private, whatever, e.t.c….) and use those as inputs. Then you can set a default output which your events through our service will be saved, with whatever settings are appropriate for that service (Google’s settings might differ from Apple’s).

simpers:

Then the service will allow groups of people to find free slots for meeting up

zachdaniel:

and when will it be google’s UI and not your own?

simpers:

People vote on those, it is saved and it gets pushed to each person’s calendar

simpers:

Sign-in och registration

simpers:

On say Android.

zachdaniel:

like…what app will be open when it happens 😆

simpers:

On Apple it’ll be a different thing, though I haven’t personally implemented this.

simpers:

My app will open, but the system’s UI will pop up over it. Usually this is done by calling some SDK from within the app and setting up some hooks 🙂

zachdaniel:

why will the system’s UI pop up over it?

simpers:

And that is also how I’ve done it in Angular previously. Used Firebase’s SDK for this

simpers:

Because that’s how it works when integrating with them haha. What do you mean?

simpers:

Like, my view will show “Sign up with any of these” and list some buttons. And depending on the click, a different SDK will be called

simpers:

Then that’ll take over

zachdaniel:

and that forces you to implement the implicit oauth flow on your service?

zachdaniel:

because they send you a token or something?

simpers:

Yes

zachdaniel:

that sounds unideal. AFAIK implicit flows are significantly less secure than regular flows, and it means your api will have to support those

simpers:

Yes

zachdaniel:

you’re sure there is no way for those SDKs you’re talking about to use the standard oauth flow? And open a browser window, like everyone else does everywhere else 😆 ?

simpers:

Everyone else on which platforms? haha

zachdaniel:

I mean, in this case I’m talking about native apps/ios, I don’t use android

zachdaniel:

can you like…do it yourself? instead of using a native SDK?

simpers:

The thing is, it may or may not open a UI in a browser if it wants to (the SDK), I don’t care. But how would browser cookies help in my native iOS app?

simpers:

I need a JWT for the API service in that app.

simpers:

Hahah well, I don’t know. I’m trying to minimise the complexity of what I have to do to get my MVP up.

simpers:

Since auth isn’t that interesting of a problem to solve. It’s been solved a million (or a billion?) times already haha

zachdaniel:

okay, so you’re using the android SDK for oauth2, this thing I assume: https://developer.android.com/training/id-auth/authenticate

simpers:

No, I’m not

simpers:

I haven’t done anything yet on the android side.

simpers:

I’m only building the frontend in Angular for now, and the server in Elixir

zachdaniel:

either way, just an example. What you end up with on one end is a token valid for the target service

zachdaniel:

right?

simpers:

The target service meaning my service? If so, yes

simpers:

Also, I think what I want is actually OpenID Connect, which is built on top of OAuth 2.0

simpers:

If I’m not mistaken.

simpers:

If I’m not mistaken, what I had to do on the Facebook integration was to start the flow on the server, wait for the client to send me a nonce to pass back to Facebook to confirm the client approved the whole ordeal.

simpers:

And as such the actual credential didn’t go in a circle and was only visible to the server.

simpers:

And they had filters and whatnot setup so that the DNS had to match with the registered IP and so on.

zachdaniel:

I’m wondering if you can just authenticate w/ google (for example) and then exchange that token w/ a regular token from your server

zachdaniel:

lol, this is <@346791515923939328>’s wheelhouse. At the end of the day I think the unfortunate answer is that implicit oauth flows (which it seems like might be what you need) are not implemented by ash_authentication .

zachdaniel:

You can likely use other libraries like assent and friends to authenticate for those cases, so ideally you don’t have to be entirely on your own

simpers:

I think you mean explicit, since implicit means the flow of the browser, as the browser implicitly just deals with this. Explicit is when you have to be explicit about sending the auth headers yourself.

simpers:

I think assent is used by ash_authentication? <:thinkies:917156078788161537>

↪ mix deps.tree | grep -C 5 assent
│   │   └── sourceror ~> 0.1 (Hex package)
│   ├── stream_data ~> 0.5.0 (Hex package)
│   └── telemetry ~> 1.1 (Hex package)
├── ash_authentication ~> 3.11 (Hex package)
│   ├── ash >= 2.5.11 and < 3.0.0-0 (Hex package)
│   ├── assent ~> 0.2 (Hex package) <--------------------
│   │   ├── certifi >= 0.0.0 (Hex package)
│   │   ├── jose ~> 1.8 (Hex package)
│   │   ├── mint ~> 1.0 (Hex package)
│   │   └── ssl_verify_fun >= 0.0.0 (Hex package

zachdaniel:

I’m not so sure

zachdaniel:

Yeah, it is

zachdaniel:

but you might need to use it directly instead of through ash_authentication

zachdaniel:

I don’t think the standard browser based flow is the implicit flow

zachdaniel:

isn’t that the “authorization code flow”?

simpers:

I think a new strategy is in place. AshAuthentication.Strategy.OIDC or something? haha

zachdaniel:

Didn’t someone get oidc working here at some point?

simpers:

This is where I read it, and though it is from 2015 it is the top result haha https://leastprivilege.com/2015/04/01/implicit-vs-explicit-authentication-in-browser-based-applications/

zachdaniel:

what the heck

simpers:

I’m just as confused as you are. I don’t know anything anymore 😅

zachdaniel:

there is an oidc strategy in ash_authentication maybe that does what you want 😆

simpers:

AshAuthentication.UserIdentity is part of this, I can see. It will declare a table for what I like to call connected identities.

simpers:

I can’t find the OIDC in the docs though, but I can see there is an issue here on discord about it

jharton:

I thought I had merged OIDC support

jharton:

But maybe it got preempted by client work.

simpers:

That could be a reason I can’t find it 😅

zachdaniel:

I just linked to the docs for it

simpers:

Well, it doesn’t show, but I can imagine it is because the AshAuthentication framework is hidden on the left.

simpers:

And I added it and tried to refresh the same link but it just reset the selection to exclude AshAuthentication

simpers:

simpers:

So it is there! It might need a guide then? 😄

Clarification: once I add the AshAuthentication to the browsable frameworks, I can manually search for OIDC again. But there seem to be a parameter in the URL missing to allow the linking to work <@197905764424089601>

jharton:

There are at least two issues open on the assent repo about mobile auth. Seems like it’s not natively supported but can be bodged.

simpers:

Okay, sure! This is not the urgent part though haha. I think we got a little side-tracked tbh

simpers:

The issue I am having currently is figuring out what the built-in plugs/router stuff provides for me

simpers:

I’d like to just set them up and them working as expected for all strategies I add with time, but when I was trying to copy my GraphQL registration test and adapt it to AshAuthentication, I couldn’t get it working <:thinkies:917156078788161537>

simpers:

Which is a simple email & pass registration thing

simpers:

Deleted message were jart was tagged - found the solution to that particular problem.

zachdaniel:

He is on vacation this week, FYI

zachdaniel:

may or may not get back to you until next week

simpers:

Ah, thanks for letting me know! 🙂 He will not be allowed to respond until he’s back haha

simpers:

Vacation should be vacation