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

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
mnkhod
So i have been using ash framework for a while and i love it. However currently the issue im having with ash framework is the error handl...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
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
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews