Skip to main content

User Svc

The user service is at the heart of 1Backend, managing users, tokens, organizations, permissions and more. Each service and human on an 1Backend network has an account in the User Svc.

This page provides a high-level overview of User Svc. For detailed information, refer to the User Svc API documentation.

Overview

The most important thing about the User Svc is that service (machine) and user (human) accounts look and function the same.

Every service you write needs to register at startup, or log in with the credentials it saves and manages if it's already registered. Just like a human.

A service account is not an admin account, it's a simple user level account. You might wonder how service-to-service calls work then.

Service to service calls

Most endpoints on 1Backend can only be called by administrators by default.

Let's take prompting. If you want to let your users prompt AIs you might write a wrapper service called User Prompter Svc with the slug user-prompter-svc.

If we look at the Add Prompt endpoint API docs, we can see that it mentions

Requires the `prompt-svc:prompt:create` permission.

To enable your service to call the Add Prompt endpoint, we need to create a grant with your service slug and the permission mentioned above:

id: "user-prompter-grant"
permissionId: "prompt-svc:prompt:create"
slugs:
- "user-prompter-svc"

You can apply these grants with an administrator account in your CI workflow with the oo CLI:

oo grant save user-prompter-grant.yaml

Tokens

The User Svc produces a JWT (JSON Web Token) upon /user-svc/login in the token.token field (see the response documentation).

You can either use this token as a proper JWT - decode it and inspect the contents, or you can just use the token to read the user account that belongs to the token with the /user-svc/user/by-token endpoint.

Decoding a token

The /user-svc/public-key will return you the public key of the User Svc which then you can use that to decode the token.

Use the JWT libraries that are available in your programming language to do that, or use the Singularon SDK if your language is supported.

Token structure

The structure of the JWT is the following:

sui: usr_dC4K75Cbp6
slu: test-user-slug-0
sri:
- user-svc:user
- user-svc:org:{org_dC4K7NNDCG}:user

The field names are kept short to save space, so perhaps the Go definition is also educational:

type Claims struct {
UserId string `json:"sui"` // `sui`: 1backend (previously singulatron) user ids
Slug string `json:"slu"` // `slu`: 1backend (previously singulatron) slug
RoleIds []string `json:"sri"` // `sri`: 1backend (previously singulatron) role ids
jwt.RegisteredClaims
}

Roles

Every user has a role, and a user token (see more about tokens on this page) produced upon login contains all the roles a user has.

id: "user-svc:admin"
name: "User Svc - Admin Role"
id: "your-svc:your-role"
name: "Your Svc - Your Role"
ownerId: "usr_eaSNcJ0BB0" # your user ID

In the below sections we'll refer to roles by their ID (such as user-svc:admin). Usually such readable strings are slugs, but in the case of roles slugs were eliminated for simplicity.

Static roles

Static roles, such as

user-svc:admin
user-svc:user

defined by the User Svc are used for role-based access control. Each role has a list of permissions associated with it. When endpoints authorize a user it can do two things:

  • Call the Is Authorized with the caller user auth headers and a permission ID to see if a given caller is authorized for that endpoint.
  • Cache the list of permissions belonging to different roles and inspect only the caller's token to see if an appropriate role is present. This has the advantage of being quicker but slightly more complex (suitable SDK functions can help here). To parse and verify a token yourself, see the Get Public Key endpoint.

If you are looking at restricting access to endpoints in other ways, you might be interested in: Policy Svc.

Custom static roles

While deceptively simple, static roles can get you far, even without any permissions associated with them. A prime use case is product subscriptions.

Let's say you have a new product called "Funny Cats Newsletter" with two subscription tiers: Pro and Ultra. You might create a service with the slug funny-cats-newsletter-svc for this product. You could have the following custom static roles created by your service (by calling the Create Role endpoint):

funny-cats-newsletter-svc:pro
funny-cats-newsletter-svc:ultra

By checking the existence of these roles in a user's token you can successfully authorize product specific features.

Dynamic roles

Dynamic roles are generated based on specific user-resource associations, offering more flexible permission management compared to static roles.

Dynamic roles look like

user-svc:org:{org_dBZRCej3fo}:admin
user-svc:org:{org_dBZRCej3fo}:user

The dynamic values must be surrounded by {} symbols. The above example is how organization roles are represented. For more about organizations see the Organizations section on this page.

These dynamic roles, like static roles are stored in the JWT tokens so it is advisable to keep them to a minimum. The organization example is an apt one here: think about how many GitHub or Google organizations you are part of. Likely even a few dozen are at the most extreme upper limit.

JWT tokens (and the dynamic they contain) are sent with each request, so try to be efficient with dynamic roles.

Owning Roles

In many endpoints such as assignRole and saveInvites, the topic of "role ownership" comes up. The basic problem is simple: just because someone has a role, it doesn't mean they can also bestow that role upon other users. In simple terms, if an admin makes someone a user, that user should not be able to make others users, as that is the privilege of the admins.

When Does a User "Own" a Role?

A user "owns" a role in the following cases:

  • Static Roles: The role ID is prefixed with the caller's slug.
  • Admin Privileges: The user is an admin and can assign both static and dynamic roles within their administrative scope.

Examples of Role Ownership

  • A user with the slug joe-doe owns roles like joe-doe:any-custom-role.
  • A user with any slug who has the role my-service:admin owns my-service:user.
  • A user with any slug who has the role user-svc:org:{orgId}:admin owns user-svc:org:{orgId}:user.

By enforcing role ownership rules, the system ensures that roles are only assigned by authorized users, preventing privilege escalation and maintaining security within the organization.

Conventions

Each role created must by prefixed by the slug of the account that created it. Said account becomes the owner of the role and only that account can edit the role.

Organizations

In the dynamic roles section we already talked about organizations, lets elaborate on them here a bit.

id: "org_eZqC0BbdG2"
name: "Acme Corporation" # Full name of the organization
slug: "acme-corporation" # URL-friendly unique identifier for the organization
createdAt: "2025-01-15T12:00:00Z" # Example ISO 8601 timestamp

Access rules

Create

Any logged in user can create an organization, provided the Organization slug is not taken yet. The creator becomes the first admin of the organization, acquiring the role of user-svc:org:{orgId}:admin role.

Membership

Admins can assign other users member (user-svc:org:{orgId}:user) or admin roles (user-svc:org:{orgId}:admin) for the organizations they administer.

Use cases

Organizations let users to freely associate with each other and create groups. Think about Discord servers, Slack workspaces, Github organizations etc.

Permissions

Conventions

Each permission created must by prefixed by the slug of the account that created it. Said account becomes the owner of the permission and only that account can add the permission to a role.

Once you (your service) own a permission (by creating it, and it being prefixed by your account slug), you can add it to any role, not just roles owned by you.

Example; let's say your service is petstore-svc. 1Backend prefers fine-grained access control, so you are free to define your own permissions, such as petstore-svc:read or petstore-svc:pet:read.

Services with multiple nodes

You might now wonder what happens when a service has multiple instances/nodes. Won't their user accounts "clash" in the User Svc? The answer to this is that from the User Svc point of view, each node/instance of a service is the same account.

This is possible because the platform is designed with services having a "Shared Database Access".

Let's say you have a Cassandra network that spans multiple Availability Zones/Regions. Your nodes will also span multiple AZs/Regions and each instance of them will log in as X Svc.