serpent

serpent

Just getting into multi-tenancy and the Ash support for it. My schema looks like:

Organisation (global, tenant entity)
Identity (global) —(has many)—> User (tenant)
User (tenant) —(has many)—> Project (tenant)
Token (global)

So if a User has a record, it means the Identity belongs to the same Organisation as the User record. Authentication is performed for Identity.

Is this reasonable so far?

Now I want to add the API key strategy. But the assumptions are based on a uni-tenant configuration, it seems, adding another strategy to what my schema calls Identity now. But I need the API keys to be associated to an organisation as well, so they need to work for User.

Can I just enable extensions: [AshAuthentication] for User? Of course I tried, but Ash complains:

    ** (Spark.Error.DslError) authentication -> tokens -> enabled?:
  The `:api_key` authentication strategy requires tokens be enabled.

But “tokens” means JWT tokens, no?

What is the recommended approach here? Or is there a flaw in my foundation?

Thanks again to the Ash team for the amazing work! :face_blowing_a_kiss:

Showing Posts 1 to 10

zachdaniel

zachdaniel

Creator of Ash

Tokens are not necessarily JWTs. It is the token resource that we use to store api keys.

zachdaniel

zachdaniel

Creator of Ash

Actually, let me double check myself on that front.

zachdaniel

zachdaniel

Creator of Ash

Yeah, I’m just misremembering the implementation. We should not be requiring that. Fixed here: fix: don't require token resource for API keys · team-alembic/ash_authentication@b95b22e · GitHub

serpent

serpent OP

Ok, tried with latest version. Now it says for User:

** (EXIT from #PID<0.94.0>) an exception was raised:
    ** (Spark.Error.DslError) authentication -> session_identifier:
  Must set `authentication.session_identifier` to either `:jti` or `:unsafe`,
unless `authentication.tokens.require_token_presence_for_authentication?` is set to `true`.

If you are seeing this error while upgrading ash_authentication, be aware that
updating this setting will log out all of your users.

When set to `:unsafe`, tokens are not revoked when the user logs out.
When set to `:jti`, we use this information to revoke tokens on logout.

We suggest setting `authentication.tokens.require_token_presence_for_authentication?` to `true`
to ensure that tokens are always present during authentication, which makes this option unnecessary.
Changing either of these settings will log out all of your users.

So I added

    tokens do
      require_token_presence_for_authentication? true
    end

(full user.ex)

leading to:

authentication -> tokens:
  ** (Spark.Options.ValidationError) required :token_resource option not found, received options: [:require_token_presence_for_authentication?]

It seems, it does rely on “tokens” somehow… :thinking:

zachdaniel

zachdaniel

Creator of Ash

Looks like just another check I need to fix :smiley:

zachdaniel

zachdaniel

Creator of Ash

You can try main again.

serpent

serpent OP

Yay, was able to create a token!

Thank you, Zach!

Found a GraphQL error message that might deserve proper handling. Just FYI, optional cosmetics for now… :wink:

{
  "data": {
    "createProject": {
      "errors": [
        {
          "message": "something went wrong. Unique error id: `892f6ed9-fce4-42e4-8f0c-827ffcf46b19`"
        }
      ],
      "result": null
    }
  }
}
[warning] `892f6ed9-fce4-42e4-8f0c-827ffcf46b19`: AshGraphql.Error not implemented for error:

** (Ash.Error.Invalid.TenantRequired) Queries against the Zeitmeister.TimeTracking.Project resource require a tenant to be specified
    (ash 3.5.24) lib/ash/error/invalid/tenant_required.ex:4: Ash.Error.Invalid.TenantRequired.exception/1
    (ash 3.5.24) lib/ash/actions/create/create.ex:602: Ash.Actions.Create.set_tenant/1

Just need to fix my scoping plug now, I guess…

zachdaniel

zachdaniel

Creator of Ash

Hmm…that one is interesting. So the reason we did that is that externally the term for a tenant might be something other than “tenant”, so it would make sense to have you implement that yourself. There is a guide on doing that IIRC.

serpent

serpent OP

Ok, I introduced some denormalisation to avoid walking the tenant tables:

Organisation (global, tenant entity) —(has many)—> ApiKey (global)
User (tenant) —(has many)—> ApiKey (global)

I want to authenticate User (tenant) via ApiKey (global):

defmodule User do
  actions do
    read :sign_in_with_api_key do
      argument :api_key, :string, allow_nil?: false
      prepare AshAuthentication.Strategy.ApiKey.SignInPreparation
    end
    # [...]
  end
  # [...]
end

User is only accessible via tenant. Tenant can be derived directly from ApiKey.

AshAuthentication says:

22:49:59.723 request_id=GE7Y6Ni6AJHyb58AAABE [warning] Authentication failed:
Bread Crumbs:
  > Error returned from: Timetracker.Accounts.User.sign_in_with_api_key

Invalid Error

* Queries against the Timetracker.Accounts.User resource require a tenant to be specified
  (ash 3.5.24) lib/ash/error/invalid/tenant_required.ex:4: Ash.Error.Invalid.TenantRequired.exception/1
  (ash 3.5.24) lib/ash/actions/read/read.ex:2564: Ash.Actions.Read.validate_multitenancy/1
  (ash 3.5.24) lib/ash/actions/read/read.ex:2433: Ash.Actions.Read.handle_multitenancy/1
  (ash 3.5.24) lib/ash/actions/read/read.ex:474: Ash.Actions.Read.do_read/5
  (ash 3.5.24) lib/ash/actions/read/read.ex:330: Ash.Actions.Read.do_run/3
  (ash 3.5.24) lib/ash/actions/read/read.ex:89: anonymous fn/3 in Ash.Actions.Read.run/3
  (ash 3.5.24) lib/ash/actions/read/read.ex:88: Ash.Actions.Read.run/3
  (ash 3.5.24) lib/ash.ex:2760: Ash.read/2
  (ash_authentication 4.9.5) lib/ash_authentication/strategies/api_key/actions.ex:26: AshAuthentication.Strategy.ApiKey.Actions.sign_in/3
  (ash_authentication 4.9.5) lib/ash_authentication/strategies/api_key/plug.ex:116: AshAuthentication.Strategy.ApiKey.Plug.call/2

Which is a valid complaint…

What’s a good way to set the tenant? Maybe I could sneak in another before_action to validate (or at least identify) the API key and fetch Organisation via ApiKey, set the context, and let AshAuth validate it again. But that would be double work and I’m not sure it would even work, it seems, AshAuth (or tenant read actions in general) bails out before it even runs (which makes sense).

Copying AshAuthentication.Strategy.ApiKey.SignInPreparation and patching it to do that is not an option either, as AshAuth becomes mutinous unless exactly that preparation is present. And again, wouldn’t run anyway.

Ideas welcome. :slightly_smiling_face:

zachdaniel

zachdaniel

Creator of Ash

Ah, interesting. I think what you can do is allow bypassing multi tenancy for that read action, since every api key will uniquely be uniquely identified.

    read :sign_in_with_api_key do
      argument :api_key, :string, allow_nil?: false
      prepare AshAuthentication.Strategy.ApiKey.SignInPreparation
      multitenancy :bypass
    end

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews