Anyone using Ash Authentication with OIDC (e.g., keycloak) ?

Hello folks,

I’m considering Ash for a new internal project, most likely a simple single BEAM node, and the idea is to leverage Ash Auth and avoid recreating a bug-ridden version of it.
Good or bad, the ecosystem here stores the users, along with their roles/permissions as claims that come in the JWT that apps see.
So my newbie questions for the nice Ash community are:

  • Any special tricks to setup keycloak as a generic OIDC provider ? Anyone that did it care to share some snippets?
  • Can one access the roles on the JWT, and use them as part of the policies? Something like “actor has_role ABC” ?
  • In the worst case, there’s any scape hatch where the auth flow happens outside Ash, and one “injects” the user + roles into Ash, making possible to leverage the policies ?
  • My understanding is that once auth happens, the user gets a browser session - does the machinery behind refresh the token, or in the OAUTH, OIDC, etc. cases, the app is stateless and the token must be presented for every request ?
  • If I don’t want to store the User / UserIdentify in a proper db, can ETS be used here?

lots of question, appreciate any replies!
Kudos on Ash, btw.

Hoi OddNibble

We did integrate Ash Authentication with Keycloak just last night for a PoC. We started by following the AshAuthentication guides on getting it to run with Phoenix. From there on you want to use the OIDC stuff which is similar to the OAuth2 flow so you can also look at those docs. Let me copy and paste some code for you as inspiration, and you can ping me if you need more help.

Additional info: we also ran the mix igniter stuff which generated a bunch of modules like Account.User which you will need, so make sure you run that!

defmodule YourApp.Accounts.User do
  authentication do
    tokens do
      enabled? true
      token_resource YourApp.Accounts.Token
      signing_secret YourApp.Secrets
    end

    strategies do
      oidc :my_oidc_provider do
        client_id "<some-client-id>"
        base_url "http://<my-keycloak-host.whatever>/auth/realms/<my-realm-name>"
        redirect_uri "http://my-elixir-host.whatever/<auth-route-that-is-defined-in-router.ex>/"
        registration_enabled? false
        client_secret "<much secret so safe>"
      end
    end

  actions do
    defaults [:read]

    read :get_by_subject do
      description "Get a user by the subject claim in a JWT"
      argument :subject, :string, allow_nil?: false
      get? true
      prepare AshAuthentication.Preparations.FilterBySubject
    end

    read :sign_in_with_my_oidc_provider do # <- needs to match the name of the oidc strategy you set at the top!
      argument :user_info, :map, allow_nil?: false
      argument :oauth_tokens, :map, allow_nil?: false
      prepare AshAuthentication.Strategy.OAuth2.SignInPreparation

      filter expr(email == get_path(^arg(:user_info), [:email])) # <- or any other field that you use to identify users with
    end
  end
  attributes do
      <some primary key that you like>
      attribute :username, :string, public? true, allow_nil?: false
      attribute :email, :string
      attribute :name, :string, public?: true
      attribute :surname, :string, public?: true
    end
  end
defmodule YourAppWeb.Router do
  scope "/", YourAppWeb do
    pipe_through :browser

    ash_authentication_live_session :authentication_required,
      on_mount: {YourAppWeb.LiveUserAuth, :live_user_required} do
        live "/", SomeLiveViewPage, :index
    end


    auth_routes AuthController, YourApp.Accounts.User, path: "/<the path that you set in the resource!>"
    sign_out_route AuthController

    # Remove these if you'd like to use your own authentication views
    sign_in_route register_path: "/register",
                  reset_path: "/reset",
                  auth_routes_prefix: "/<the path you set in the resource!>",
                  on_mount: [{YourAppWeb.LiveUserAuth, :live_no_user}],
                  overrides: [
                    YourAppWeb.AuthOverrides,
                    AshAuthentication.Phoenix.Overrides.Default
                  ]
  end
end

Then of course you also need to create a suitable oidc client in keycloak. You want a private one with secret, you don’t need to configure anything special.

Hope this helps.

Gruess

1 Like

Awesome! thanks a lot for the snippet.