Authentication.Plug - What to do as an API? I use Phoenix, but not with views. What is provided?
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
jharton:
Oh yeah. It did get merged. https://github.com/team-alembic/ash_authentication/blob/main/lib/ash_authentication/strategies/oidc.ex
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