Skip to content

Concepts

Authentication

This page shows how a Python application signs in to SurrealDB with the SDK. It covers the four sign-in levels, sign-up for record users, access tokens and refresh tokens, the auth option that signs a connection in by itself, automatic token renewal, and signing out.

Method

Description

db.signin(vars)

Signs in as a root, namespace, database or record user

db.signup(vars)

Signs up a new record user with a record access method

db.authenticate(token)

Authenticates the session with an access token

Surreal(url, auth=...)

Makes the connection sign itself in

db.info()

Returns the record of the signed-in record user

db.invalidate()

Signs the session out

db.reset()

Clears the namespace, database, variables and sign-in of the session

db.refresh(tokens?)

Exchanges a refresh token for a new access token and refresh token

db.revoke(tokens?)

Revokes a refresh token, so that it cannot be exchanged again

signin() signs the session in. The fields that you pass choose the level of the user. It returns Tokens, which holds the access token and, for an access method defined WITH REFRESH, a refresh token. See the API reference for every field at each level.

A root user has access to the whole SurrealDB instance. Pass the username and password.

from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()

tokens = db.signin({
    "username": "root",
    "password": "secret",
})

The examples use the blocking API. On an asynchronous connection, await each call.

When a namespace, database or record user signs in, the server also selects the namespace of that user, and for a database or record user, the database as well. A namespace user still needs use() to select a database.

The repr of Tokens hides both values, so printing or logging it does not expose them. For the root user, it prints:

print(tokens)
Output
Tokens(access='<redacted>', refresh=None)

Read tokens.access and tokens.refresh when you need the values, for example to store them.

Each session from new_session() starts with the sign-in of the connection, and can sign in as another user without changing the connection. See Multiple sessions.

signup() creates a record user with a record access method and signs the session in as that user. It returns Tokens, like signin(). Sign-up is only available for record access methods.

The examples on this page use the record access method below. Define it as a root, namespace or database user, for example with db.query(). Its WITH REFRESH clause makes it issue a refresh token as well, which Refresh tokens uses.

DEFINE TABLE person
	PERMISSIONS FOR select, update WHERE id = $auth.id;

DEFINE ACCESS account ON DATABASE TYPE RECORD
	SIGNUP (
		CREATE person CONTENT {
			email: $email,
			pass: crypto::argon2::generate($password)
		}
	)
	SIGNIN (
		SELECT * FROM person WHERE email = $email
			AND crypto::argon2::compare(pass, $password)
	)
	WITH REFRESH
	DURATION FOR TOKEN 15m;

Pass the namespace, database and access, and the variables that the SIGNUP clause reads:

from surrealdb import Surreal

db = Surreal("ws://localhost:8000")
db.connect()

tokens = db.signup({
    "namespace": "surrealdb",
    "database": "docs",
    "access": "account",
    "variables": {
        "email": "tobie@surrealdb.com",
        "password": "hunter123",
    },
})

The auth option of Surreal() and AsyncSurreal() makes the connection sign itself in, so that you do not need to call signin() yourself. Pass the same credentials that signin() takes:

from surrealdb import RecordID, Surreal

with Surreal(
    "ws://localhost:8000",
    auth={"username": "root", "password": "secret"},
) as db:
    db.use("surrealdb", "docs")
    db.create(RecordID("person", "jaime"), {"name": "Jaime"})

auth takes four kinds of value:

ValueWhat the connection does
A dict of credentialsSigns in with them, as signin() does.
A strAuthenticates with it as an access token, as authenticate() does.
TokensAuthenticates with the access token, and keeps the refresh token to renew it. If the server refuses the access token, for example because it has expired, the connection renews it with the refresh token.
A function with no argumentsCalls the function each time that the connection signs in, and uses the value that it returns, which is one of the values above. On AsyncSurreal it can be a coroutine function.

A function lets the connection read credentials or a token each time it needs them, for example from the environment or from a secret store:

import os

from surrealdb import Surreal


def credentials():
    return {
        "username": "root",
        "password": os.environ["SURREALDB_PASSWORD"],
    }


db = Surreal("ws://localhost:8000", auth=credentials)

A coroutine function given to Surreal() raises TypeError when the connection signs in, because a blocking connection cannot await it.

A WebSocket connection applies auth when its socket opens, and an HTTP connection applies it before its first request. After an automatic reconnect, the connection signs in again with the credentials, or calls the function again. A token or Tokens given directly is not applied again. The connection restores the tokens that it holds at that time instead, which are newer ones if a renewal has replaced them.

The connection also applies auth again in three cases: after close() and connect(), after connect() with a new URL, and after a WebSocket drops while reconnection is off. An embedded connection ignores auth.

A call to signin(), signup(), authenticate(), invalidate() or reset() on the connection's own session replaces the sign-in that auth gave. From then on, renewal uses only the refresh token of the session, and a reconnect restores the sign-in that you chose. auth applies again only in the three cases above:

from surrealdb import Surreal

db = Surreal(
    "ws://localhost:8000",
    auth={"username": "root", "password": "secret"},
)
db.connect()

db.signin({
    "namespace": "surrealdb",
    "database": "docs",
    "access": "account",
    "variables": {
        "email": "tobie@surrealdb.com",
        "password": "hunter123",
    },
})
print(db.query("RETURN session::ac()").first())

db.close()
db.connect()
print(db.query("RETURN session::ac()").first())
db.close()
Output
account
None

The first query runs as the record user of the account access method. After close() and connect(), the connection is the root user of auth again, which has no access method. A sign-in on a session from new_session() does not change the connection's own session, so auth keeps applying to it.

authenticate() signs the session in with an access token that you already have, for example one that signin() returned to another part of your application, or one that a client sent to your server:

db.authenticate(tokens.access)

A string that does not have the form of a JWT raises ValueError before anything is sent. A token that the server refuses raises NotAllowedError over a WebSocket, and HttpStatusError with status 401 over HTTP. Over HTTP, a refused token does not replace the sign-in that the connection had before.

info() returns the record of the signed-in record user:

user = db.info()
print(user["email"])
Output
tobie@surrealdb.com

info() returns None for a root, namespace or database user, when a namespace and database are selected. Without them, it raises ValidationError. It also returns None for a record user whose table permissions do not let them select their own record. The PERMISSIONS clause on person above lets each user select their own record.

invalidate() signs the session out. Requests after it run without authentication.

db.invalidate()

reset() returns the session to how it started: it clears the namespace, the database, the variables and the sign-in. This lets you reuse a connection for different work, for example to return a pooled connection in a clean state. It needs SurrealDB 3.0 or later.

db.let("greeting", "hello")
db.reset()
print(db.namespace, db.database, db.token)
Output
None None None

Both methods replace the sign-in that the auth option gave, as described in Signing in on a connection that uses auth.

An access method defined WITH REFRESH returns a refresh token with each sign-in and sign-up, in tokens.refresh. The client can exchange a refresh token once for a new access token and a new refresh token, so that the access token can be short-lived without the user signing in again. Refresh tokens need SurrealDB 3.0 or later. Against SurrealDB 2.x, refresh() and revoke() raise UnavailableFeatureError.

refresh() exchanges the refresh token for a new pair of tokens, authenticates the session with the new access token, and returns the new Tokens. Without an argument, it uses the tokens that the session holds. Pass Tokens to exchange other ones.

from surrealdb import Surreal

with Surreal("ws://localhost:8000") as db:
    tokens = db.signin({
        "namespace": "surrealdb",
        "database": "docs",
        "access": "account",
        "variables": {
            "email": "tobie@surrealdb.com",
            "password": "hunter123",
        },
    })
    print(tokens)

    renewed = db.refresh()
    print(renewed.refresh == tokens.refresh)
Output
Tokens(access='<redacted>', refresh='<redacted>')
False

The exchange spends the old refresh token. If you exchange it again, the server refuses it and the SDK raises NotAllowedError.

To sign in later with a stored refresh token, for example in a new process, pass it as refresh in place of variables. The sign-in spends that refresh token and returns a new pair. When the user signs out, revoke() makes the refresh token of the session unusable, and invalidate() ends the sign-in:

with Surreal("ws://localhost:8000") as db:
    tokens = db.signin({
        "namespace": "surrealdb",
        "database": "docs",
        "access": "account",
        "refresh": renewed.refresh,
    })

    db.revoke()
    db.invalidate()

revoke() does not end the sign-in: the session stays signed in with its access token until that token expires or invalidate() is called. refresh() and revoke() raise ValueError when you pass no Tokens and the session holds no refresh token, for example after revoke().

With renew_access=True, the default, a connection renews its own access token before the token expires: 60 seconds before, or a tenth of the token's lifetime before if that is shorter. It renews with its refresh token if it holds one and the server supports refresh tokens, and otherwise by applying the auth option again.

A connection created with auth credentials or a function can renew by signing in again. A connection that signed in with signin() renews only with a refresh token. A token or Tokens given to auth cannot be applied again, so without a refresh token such a connection cannot renew either.

The asynchronous WebSocket connection renews on a timer. The blocking connection and the HTTP connections have no background work, so they renew before the next request that finds the token due.

A failed renewal is reported as an "error" event on a WebSocket connection, and the connection tries again later. A refresh token that the server refuses is dropped. The connection then falls back to auth, or keeps the access token that it has.

Renewal keeps two kinds of connection working past the token duration: an HTTP connection, which sends the token with each request, and a WebSocket session that authenticated with a token. A WebSocket session that signed in with credentials does not depend on the token, as Connecting to SurrealDB shows. renew_access=False turns renewal off.

Over a WebSocket, the server holds the session of the connection. You sign in once, and the session keeps its namespace, database and sign-in until the session or the connection ends. The token property holds the access token of the connection's own session.

Over HTTP, the SDK holds the sign-in. It sends the access token as a bearer token, and the namespace and database as headers, with each request. A token that has expired or that the server refuses raises HttpStatusError with status 401, not NotAllowedError. token is a plain attribute on an HTTP connection, and assigning a new token to it sends that token from the next request on. This assignment replaces the set_token() method of SDK 2.x: see Migrating to 3.x.

An embedded connection needs no sign-in. Its session starts with full access, as an owner of the engine. The engine has no root user, so signin() with root credentials raises NotAllowedError, and auth and renew_access are ignored. invalidate() leaves the session anonymous and without access. See Embedded databases.

Was this page helpful?