Authentication and Authorization - SurrealTools/Documentation GitHub Wiki

Authentication

As a developer you have complete control over the signup and signin functionality..

USE NS my_ns DB my_db;
DEFINE TABLE user SCHEMAFULL PERMISSIONS FOR select, update WHERE id = $auth.id FOR create, delete NONE;
DEFINE SCOPE allusers
    -- the JWT session will be valid for 14 days
    SESSION 14d
    -- The optional SIGNUP clause will be run when calling the signup method for this scope
    -- It is designed to create or add a new record to the database.
    -- If set, it needs to return a record or a record id
    -- The variables can be passed in to the signin method
    SIGNUP ( CREATE user SET settings.marketing = $marketing, email = $email, pass = crypto::argon2::generate($pass), tags = $tags )
    -- The optional SIGNIN clause will be run when calling the signin method for this scope
    -- It is designed to check if a record exists in the database.
    -- If set, it needs to return a record or a record id
    -- The variables can be passed in to the signin method
    SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) ) /* this optional clause will be run when calling the signup method for this scope */

Then you could use the following query from the client to signup (create) a new user, or signin (select) an existing user...

db.signup({
    NS: 'my_ns',
    DB: 'my_db',
    SC: 'allusers', // We want to signup to the 'allusers' scope defined above
    email: '[email protected]', // We can add any variable here to use in the SIGNUP clause
    pass: 'some password', // We can add any variable here to use in the SIGNUP clause
    marketing: true, // We can add any variable here to use in the SIGNUP clause
    tags: ['rust', 'golang', 'javascript'], // We can add any variable here to use in the SIGNUP clause
});
db.signin({
    NS: 'my_ns',
    DB: 'my_db',
    SC: 'allusers', // We want to signup to the 'allusers' scope defined above
    email: '[email protected]', // We can add any variable here to use in the SIGNUP clause
    pass: 'some password', // We can add any variable here to use in the SIGNUP clause
});

Then finally, the record that is returned in the SIGNUP or SIGNIN clause can be used in other queries, once authenticated. The root sign in doesn't return anything. The other sign in options; scope, namespace and database do indeed return a JWT.

SELECT * FROM browsing_history WHERE user = $auth.id;
SELECT * FROM programming_language WHERE name IN $auth.tags;

You are also able to see what scope was authenticated for the user...

SELECT * FROM $scope;

And therefore you can limit what the users can see based on scope (for example have different scopes for admins or users)...

DEFINE TABLE system_settings SCHEMALESS PERMISSIONS WHERE $scope = 'admin';

Remember that you can add as many scopes as you want (for different levels of users), and your permissions can check for these different scopes. The object sent to the signup/signin method can be anything.... If authenticating as a root user...

{
  user: 'root',
  pass: 'root',
}

If authenticating as a NAMESPACE user...

USE NS test;
DEFINE LOGIN my_login ON NAMESPACE PASSWORD '123456';
db.signin({
  NS: 'test',
  user: 'my_login',
  pass: '123456',
});

If authenticating as a DATABASE user...

USE NS test DB test;
DEFINE LOGIN my_login ON DATABASE PASSWORD '123456';
db.signin({
  NS: 'test',
  DB: 'test',
  user: 'my_login',
  pass: '123456',
});

If authenticating as a SCOPE user...

USE NS test DB test;
DEFINE SCOPE my_scope SESSION 24h
  SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass), interests = $interests )
  SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
;
db.signup({
  NS: 'test',
  DB: 'test',
  SC: 'mu_scope',
  email: '[email protected]',
  pass: '123456',
  interests: ['my', 'hobbies'],
});
db.signin({
  NS: 'test',
  DB: 'test',
  SC: 'mu_scope',
  email: '[email protected]',
  pass: '123456',
});

The email and user would only be specified if needed in the SIGNUP or SIGNIN clause... As a user you can choose to use any variable names in the SIGNUP / SIGNIN query, and no limit on what variables you pass in either... if using SurrealDB from server side (without permissions / authentication scopes), then you would just use one of the ROOT/NS/DB user types. When using SCOPES, then in addition to the NS, DB, SC keys, you can pass any parameters, and those become parameters in the SIGNUP and SIGNIN clause in the SCOPE definition...

If authenticating as a root user, you can use basic authentication, or...

db.signin({
  user: 'root',
  pass: 'root',
});

If authenticating as a NAMESPACE user...

USE NS test;
DEFINE LOGIN my_login ON NAMESPACE PASSWORD '123456';
db.signin({
  NS: 'test',
  user: 'my_login',
  pass: '123456',
});

If authenticating as a DATABASE user...

USE NS test DB test;
DEFINE LOGIN my_login ON DATABASE PASSWORD '123456';
db.signin({
  NS: 'test',
  DB: 'test',
  user: 'my_login',
  pass: '123456',
});

If authenticating as a SCOPE user...

USE NS test DB test;
DEFINE SCOPE my_scope SESSION 24h
  SIGNUP ( CREATE user SET email = $email, pass = crypto::argon2::generate($pass), interests = $interests )
  SIGNIN ( SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(pass, $pass) )
;
db.signup({
  NS: 'test',
  DB: 'test',
  SC: 'mu_scope',
  email: '[email protected]',
  pass: '123456',
  interests: ['my', 'hobbies'],
});
db.signin({
  NS: 'test',
  DB: 'test',
  SC: 'mu_scope',
  email: '[email protected]',
  pass: '123456',
});

Authenticated User Record

You can access the user record which is authenticated with...

SELECT * FROM $auth;
-- To fetch a field on the user record...
SELECT * FROM $auth.email;
SELECT * FROM $auth.username;
-- And use it in queries
SELECT * FROM payment WHERE user = $auth;
-- Or with a field
SELECT * FROM payment WHERE username = $auth.username;
DEFINE TABLE restaurant SCHEMALESS
  PERMISSIONS
    FOR select FULL
    FOR create, update, delete WHERE $scope = "admin" AND $auth.admin = true
;

$token (in 1.0.0-beta.8) can access all the fields in the JWT $auth is the reference to the record which is returned from the SCOPE SIGNIN/SIGNUP clause

  • $auth will refer to the record which was returned in the SIGNUP or SIGNIN query.
  • $scope will be the name of the scope that the user signed in to
  • $token will be the JWT token contents itself (coming in 1.0.0-beta.8)
  • $session has various data about the session including ip/origin/ns/db/sc/auth data

Permissions

You can define permissions on a TABLE or FIELD

DEFINE TABLE person SCHEMALESS PERMISSIONS FOR select FULL FOR create, update, delete WHERE $audh.admin = true;
DEFINE FIELD password ON TABLE person TYPE string PERMISSIONS NONE;

Q and A

**Question: ** Is it possible to set permissions for objects stored in an array?

Answer This isn't possible (yet) because the array item doesn't know which item it is. You can do it with specific array ids, but obviously it doesn't have the same simplicity

DEFINE TABLE lobby
  PERMISSIONS
    FOR select FULL
    FOR create, update, delete NONE;
DEFINE FIELD game ON lobby TYPE object;
DEFINE FIELD game.roles ON lobby TYPE array;
DEFINE FIELD game.roles[0] ON lobby TYPE object;
DEFINE FIELD game.roles[0].player ON lobby TYPE string;
DEFINE FIELD game.roles[0].role ON lobby TYPE string
  PERMISSIONS
    FOR select WHERE game.roles[0].player = $auth.id
    FOR update, create, delete NONE;
DEFINE FIELD game.roles[1] ON lobby TYPE object;
DEFINE FIELD game.roles[1].player ON lobby TYPE string;
DEFINE FIELD game.roles[1].role ON lobby TYPE string
  PERMISSIONS
    FOR select WHERE game.roles[1].player = $auth.id
    FOR update, create, delete NONE;

Question: For permissions, can I access scope as a variable?

Answer: $scope is a specific parameter.

DEFINE TABLE lobby PERMISSIONS FOR SELECT WHERE $scope = "admin";

The $auth parameter refers to the currently selected record in the SIGNUP / SIGNIN clause in the SCOPE. So effectively the logged in user or something like that.

SELECT * FROM $auth.account; -- This would select the accunt field from the logged in user

Question: If I have seven user roles, would assigning each to a different scope make the most sense?

Answer: It's very flexible, I guess it depends on how the user roles will login. Are the different users in different tables? Usually if you have an admin table and a user table, you would use a different scope for signin for each. If the users signin the same way, but there is a field on the user record which denotes what permissions levels they have, thenyou could just do one scope.Once you have signed in the user, you can access the user record in PERMISSION clauses by using $auth.

DEFINE TABLE order PERMISSIONS FOR update WHERE $auth.permissions.update_orders = true;

Question: Create a way for admin users to sign in (you can also use external auth for this)...

DEFINE SCOPE admin SESSION 1h
  SIGNIN ( SELECT * FROM admin WHERE email = $email AND crypto::argon2::compare(pass, $pass) );
-- You can only update the role of a user if you are logged in as an admin user to the 'admin' scope.
DEFINE FIELD role ON TABLE user
  PERMISSIONS
    FOR select FULL,
    FOR create, update, delete WHERE $scope = 'admin';

So then if a user did try to update their role (and they aren't logged in as an admin user), then the field change will be ignored. So it doesn't matter what they 'inject' or how they change the query - they can be blocked from doing this they shouldn't be able to do.