ABAC replacement for public/unauthenticated client access?

Hi. I see that client keys and roles and the public permission were deprecated in 2.12.0 in favor of ABAC. I see in the docs how to use ABAC for authenticated users, but what’s the best practice for unauthenticated?

I have two scenarios. In one, most like the client role access, unauthenticated users only have read access. Perhaps I just create a single user with an empty password to represent all unauthenticated users and use that for ABAC? And then make it impossible to sign into that user normally?

In the other scenario, unauthenticated users can create content and then I want them to be able to create an account and claim their content. If they never create an account then I can clean up unclaimed content, as well as their unclaimed unauthenticated user records, out in background jobs. Perhaps I create multiple users for unauthenticated users with empty passwords in that case, and “creating an account” means adding a real user name and password to the formerly-anonymous user record?

If I missed docs about this, apologies: please send them over. :slight_smile:

Thanks,
Gary

Hi Gary,

In the most simple scenario you would create a bootstrap role that has two permissions:

  • Permissions to log in. This could be access to an user_by_email index or just the right to call a User Defined Function or UDF that logs your user in.
  • Permissions to register. This could be access to create a new user or again a UDF

Note: I prefer UDFs in such case, since it’s a convenient way to bundle some logic together such as searching a user by e-mail, calling login etc.

For example, your bootstrap role could look like this (non UDF approach):

{
  name: 'keyrole_register_login_verify_token',
  privileges: [
    {
      resource: Collection('accounts'),
      // accounts can be created.
      // accounts can be updated to change the verification
      actions: {
        create: true,
        read: true
      }
    },
    {
      resource: Index('accounts_by_email'),
      actions: { read: true }
    }
  ]
}

For which you then can create a key (with the CreateKey function) to bootstrap your app. You do not need to create a fake user and login with that, if you do not need to have an Identity (see the Identity) function) to be attached to the secret than you don’t need that. If that initial access is however not anonymous you can make a Token as well (Keys are anonymous, Tokens have an identity or database document attached to them) by using

Create(Tokens(), {
        instance: <reference to some database entity> 
}

This gives you a similar token as Login would give you, only without the credentials requirement.
Tokens are documented (and creation will soon be) documented here
Keys documentation can be found here.

Your scenario

The above assumes no read access to your resources accept what is necessary to login. For your first scenario:

  • add read access to the resources you want to make publicly available

Second scenario:

  • You are on the right track I think to have passwordless users there. You can use the passwordless token approach above for that (maybe via an UDF) and once the user is claimed, update the document to add credentials and revert to Login to generate your tokens.

Example

There is an example here (with Github repo) that creates ‘client roles’ as well as ‘logged in’ roles.

3 Likes

That’s fantastic help @databrecht. Thank you. I see the appeal of UDFs for the permissions. And I had seen the existence of the Fwitter article but hadn’t actually clicked on it before: yes, that’s a great resource.

I do have one follow-on.

For the second scenario, in which I want both authenticated and unauthenticated users to be able to create owned content that can then follow the user through claiming an account, that requires an Identity function with an actual empty password, right? Or else I’d have to store and handle user identity differently depending on whether the user was authenticated?

This feels like the same story for building an app that’s an SSO consumer–using federated identity from another source. Wouldn’t that need to use Identity but with an empty password also?

Yes, absolutely, SSO would need the same. You would create a token in the backend based on another token so there is no password checking. Your backend just decides that the token from the external party is valid and exchanges it for a Fauna token. And the main reason you do that is that this external token doesn’t know about Fauna’s role system so you have to exchange it with a FaunaDB token to actually use the Identity() function and be able to use our ABAC system. That backend of course needs the permissions to create a token.

But I don’t get why you mention empty password. Think of it differently, it’s just a token that is created using the

Create(Tokens(), {
        instance: <reference to some database entity> 
}

syntax. And that is, of course, done by a part of your application (backend) that has a Key that allows you to create tokens (You can add ‘Tokens()’ as a resource to a role instead of a collection via FQL, I don’t think the dashboard allows that).

While Login is actually a combination of

  • Identify() -> check whether the password is correct
  • Create(Tokens()… -> make the token if it was correct.

You can perfectly implement Login yourself with these. What I’m saying is, although we offered Login at first and added Create(Tokens()… ) later. Login is actually the special case that checks a password, which allows you to create tokens without requiring a backend that has permissions to creat etokens. For the rest, it’s just all about creating tokens :).

4 Likes

Oh! I get it! Perfect, thank you. :slight_smile: