Users Collection Guide - Swift SDK Documentation

This guide explains how to use the built-in users collection for authentication, registration, and API rules in the Swift SDK.

Table of Contents


Overview

The users collection is a special auth collection that comes pre-configured in BosBase. It provides:

  • User authentication (password, OAuth2, OTP)
  • User registration
  • Email verification
  • Password reset
  • Built-in API rules for user data access

The users collection is automatically created when you set up a new BosBase instance.


Collection Structure

Get Users Collection Schema

import BosBase

let client = try BosBaseClient(baseURLString: "http://127.0.0.1:8090")

let usersCollection: JSONRecord = try await client.collections.getOne("users")

if let name = usersCollection["name"]?.value as? String,
   let type = usersCollection["type"]?.value as? String {
    print("Collection: \(name) (\(type))")
}

if let schema = usersCollection["schema"]?.value as? [JSONRecord] {
    print("Fields: \(schema.count)")
    for field in schema {
        if let fieldName = field["name"]?.value as? String {
            print("  - \(fieldName)")
        }
    }
}

System Fields

The users collection includes several system fields that are automatically managed:

id

  • Type: text
  • Description: Unique record identifier
  • System: Yes
  • Required: Yes

created

  • Type: date
  • Description: Record creation timestamp
  • System: Yes
  • Required: Yes

updated

  • Type: date
  • Description: Last update timestamp
  • System: Yes
  • Required: Yes

email

  • Type: email
  • Description: User’s email address (used for authentication)
  • System: Yes
  • Required: Yes
  • Unique: Yes

verified

  • Type: bool
  • Description: Email verification status
  • System: Yes
  • Default: false

emailVisibility

  • Type: bool
  • Description: Whether email is visible to other users
  • System: Yes
  • Default: false

username

  • Type: text
  • Description: Unique username (optional)
  • System: Yes
  • Required: No
  • Unique: Yes (if provided)

name

  • Type: text
  • Description: User’s display name
  • System: Yes
  • Required: No

avatar

  • Type: file
  • Description: User’s profile picture
  • System: Yes
  • Required: No

passwordHash

  • Type: text
  • Description: Hashed password (never exposed in API responses)
  • System: Yes
  • Hidden: Yes

lastResetSentAt

  • Type: date
  • Description: Timestamp of last password reset email
  • System: Yes
  • Hidden: Yes

lastVerificationSentAt

  • Type: date
  • Description: Timestamp of last verification email
  • System: Yes
  • Hidden: Yes

Custom Fields

You can add custom fields to the users collection:

// Authenticate as superuser
try await client
    .collection("_superusers")
    .authWithPassword(identity: "admin@example.com", password: "password")

// Get current schema
let usersCollection: JSONRecord = try await client.collections.getOne("users")
var schema = usersCollection["schema"]?.value as? [JSONRecord] ?? []

// Add custom field
schema.append([
    "name": AnyCodable("bio"),
    "type": AnyCodable("text"),
    "required": AnyCodable(false),
    "options": AnyCodable([
        "min": AnyCodable(0),
        "max": AnyCodable(500)
    ])
])

// Update collection
_ = try await client.collections.update("users", body: [
    "schema": AnyCodable(schema)
])

Example Custom Fields:

// Add phone number
schema.append([
    "name": AnyCodable("phone"),
    "type": AnyCodable("text"),
    "required": AnyCodable(false)
])

// Add date of birth
schema.append([
    "name": AnyCodable("dateOfBirth"),
    "type": AnyCodable("date"),
    "required": AnyCodable(false)
])

// Add preferences (JSON field)
schema.append([
    "name": AnyCodable("preferences"),
    "type": AnyCodable("json"),
    "required": AnyCodable(false)
])

Default API Rules

The users collection has default API rules that control access:

List Rule

  • Default: "" (empty - only authenticated users can list)
  • Description: Controls who can list users

View Rule

  • Default: "id = @request.auth.id" (users can only view their own record)
  • Description: Users can only view their own profile

Create Rule

  • Default: "" (empty - anyone can create/register)
  • Description: Allows public user registration

Update Rule

  • Default: "id = @request.auth.id" (users can only update their own record)
  • Description: Users can only update their own profile

Delete Rule

  • Default: "id = @request.auth.id" (users can only delete their own record)
  • Description: Users can only delete their own account

Manage Rule

  • Default: "" (empty - only superusers)
  • Description: Controls who can manage user records (superuser only)

Auth Rule

  • Default: "" (empty - anyone can authenticate)
  • Description: Controls who can authenticate (public by default)

Get Current Rules

let usersCollection: JSONRecord = try await client.collections.getOne("users")

if let options = usersCollection["options"]?.value as? JSONRecord,
   let listRule = options["listRule"]?.value as? String {
    print("List rule: \(listRule)")
}

if let options = usersCollection["options"]?.value as? JSONRecord,
   let viewRule = options["viewRule"]?.value as? String {
    print("View rule: \(viewRule)")
}

Update Rules

// Authenticate as superuser
try await client
    .collection("_superusers")
    .authWithPassword(identity: "admin@example.com", password: "password")

// Get current options
let usersCollection: JSONRecord = try await client.collections.getOne("users")
var options = usersCollection["options"]?.value as? JSONRecord ?? [:]

// Update rules
options["listRule"] = AnyCodable("verified = true") // Only verified users visible
options["viewRule"] = AnyCodable("id = @request.auth.id || verified = true") // Own record or verified users
options["createRule"] = AnyCodable("") // Public registration
options["updateRule"] = AnyCodable("id = @request.auth.id") // Only own record
options["deleteRule"] = AnyCodable("id = @request.auth.id") // Only own record

// Update collection
_ = try await client.collections.update("users", body: [
    "options": AnyCodable(options)
])

User Registration

Public Registration

Users can register themselves:

// Register a new user
let record: JSONRecord = try await client
    .collection("users")
    .create(body: [
        "email": AnyCodable("user@example.com"),
        "password": AnyCodable("password123"),
        "passwordConfirm": AnyCodable("password123"),
        "name": AnyCodable("John Doe")
    ])

print("User registered: \(record["id"]?.value ?? "")")

Registration with Email Verification

// Register user
let record: JSONRecord = try await client
    .collection("users")
    .create(body: [
        "email": AnyCodable("user@example.com"),
        "password": AnyCodable("password123"),
        "passwordConfirm": AnyCodable("password123")
    ])

// Request verification email
try await client
    .collection("users")
    .requestVerification(email: "user@example.com")

// User clicks link in email, then verify
try await client
    .collection("users")
    .confirmVerification(token: "verification-token")

Registration as Superuser

// Authenticate as superuser first
try await client
    .collection("_superusers")
    .authWithPassword(identity: "admin@example.com", password: "password")

// Create user (bypasses createRule)
let user: JSONRecord = try await client
    .collection("users")
    .create(body: [
        "email": AnyCodable("user@example.com"),
        "password": AnyCodable("password123"),
        "passwordConfirm": AnyCodable("password123"),
        "verified": AnyCodable(true) // Auto-verify
    ])

User Login

Password Authentication

let authResult: AuthResult = try await client
    .collection("users")
    .authWithPassword(identity: "user@example.com", password: "password123")

print("Authenticated: \(authResult.record["email"]?.value ?? "")")
print("Token: \(authResult.token)")

OAuth2 Authentication

// Get OAuth2 providers
let authMethods: AuthMethodsList = try await client
    .collection("users")
    .listAuthMethods()

if let oauth2 = authMethods.oauth2?["google"] {
    // Redirect user to oauth2.url
    // After redirect:
    let authResult: AuthResult = try await client
        .collection("users")
        .authWithOAuth2Code(provider: "google", code: "code-from-redirect")
}

OTP Authentication

// Request OTP
try await client
    .collection("users")
    .requestOTP(email: "user@example.com")

// Authenticate with OTP
let authResult: AuthResult = try await client
    .collection("users")
    .authWithOTP(email: "user@example.com", otp: "123456")

Check Authentication Status

if let authRecord = client.authStore.record {
    print("Authenticated as: \(authRecord["email"]?.value ?? "")")
} else {
    print("Not authenticated")
}

Using @request.auth in API Rules

The @request.auth variable refers to the currently authenticated user record. Use it in API rules to control access based on the authenticated user.

Example: User-Specific Data

// Create a collection with user-specific access
let collection: JSONRecord = try await client.collections.create(body: [
    "name": AnyCodable("user_posts"),
    "type": AnyCodable("base"),
    "schema": AnyCodable([
        [
            "name": AnyCodable("title"),
            "type": AnyCodable("text"),
            "required": AnyCodable(true)
        ],
        [
            "name": AnyCodable("user"),
            "type": AnyCodable("relation"),
            "options": AnyCodable([
                "collectionId": AnyCodable("users"),
                "maxSelect": AnyCodable(1)
            ])
        ]
    ]),
    "options": AnyCodable([
        "listRule": AnyCodable("user.id = @request.auth.id"),
        "viewRule": AnyCodable("user.id = @request.auth.id"),
        "createRule": AnyCodable("user.id = @request.auth.id"),
        "updateRule": AnyCodable("user.id = @request.auth.id"),
        "deleteRule": AnyCodable("user.id = @request.auth.id")
    ])
])

Example: Public Read, User Write

let collection: JSONRecord = try await client.collections.create(body: [
    "name": AnyCodable("posts"),
    "type": AnyCodable("base"),
    "schema": AnyCodable([
        [
            "name": AnyCodable("title"),
            "type": AnyCodable("text")
        ],
        [
            "name": AnyCodable("author"),
            "type": AnyCodable("relation"),
            "options": AnyCodable([
                "collectionId": AnyCodable("users"),
                "maxSelect": AnyCodable(1)
            ])
        ]
    ]),
    "options": AnyCodable([
        "listRule": AnyCodable(""), // Public read
        "viewRule": AnyCodable(""), // Public read
        "createRule": AnyCodable("@request.auth.id != ''"), // Authenticated users only
        "updateRule": AnyCodable("author.id = @request.auth.id"), // Only author can update
        "deleteRule": AnyCodable("author.id = @request.auth.id") // Only author can delete
    ])
])

Example: User Profile Access

// Users can view their own profile and verified users
let usersCollection: JSONRecord = try await client.collections.getOne("users")
var options = usersCollection["options"]?.value as? JSONRecord ?? [:]

options["viewRule"] = AnyCodable("id = @request.auth.id || verified = true")

_ = try await client.collections.update("users", body: [
    "options": AnyCodable(options)
])

Creating Relations to Users

One-to-One Relation

let collection: JSONRecord = try await client.collections.create(body: [
    "name": AnyCodable("profiles"),
    "type": AnyCodable("base"),
    "schema": AnyCodable([
        [
            "name": AnyCodable("user"),
            "type": AnyCodable("relation"),
            "required": AnyCodable(true),
            "options": AnyCodable([
                "collectionId": AnyCodable("users"),
                "cascadeDelete": AnyCodable(true),
                "maxSelect": AnyCodable(1)
            ])
        ],
        [
            "name": AnyCodable("bio"),
            "type": AnyCodable("text")
        ]
    ])
])

Many-to-Many Relation

// Create a junction collection for many-to-many
let collection: JSONRecord = try await client.collections.create(body: [
    "name": AnyCodable("user_follows"),
    "type": AnyCodable("base"),
    "schema": AnyCodable([
        [
            "name": AnyCodable("follower"),
            "type": AnyCodable("relation"),
            "required": AnyCodable(true),
            "options": AnyCodable([
                "collectionId": AnyCodable("users"),
                "maxSelect": AnyCodable(1)
            ])
        ],
        [
            "name": AnyCodable("following"),
            "type": AnyCodable("relation"),
            "required": AnyCodable(true),
            "options": AnyCodable([
                "collectionId": AnyCodable("users"),
                "maxSelect": AnyCodable(1)
            ])
        ]
    ]),
    "options": AnyCodable([
        "listRule": AnyCodable("follower.id = @request.auth.id || following.id = @request.auth.id"),
        "createRule": AnyCodable("follower.id = @request.auth.id")
    ])
])

Querying User Relations

// Get user with related data
let user: JSONRecord = try await client
    .collection("users")
    .getOne("user-id", expand: "profile,posts")

// Get posts by user
let posts: ListResult<JSONRecord> = try await client
    .collection("posts")
    .getList(
        filter: "author.id = 'user-id'",
        expand: "author"
    )

Best Practices

1. Email Verification

Always verify user emails for security:

// After registration
try await client
    .collection("users")
    .requestVerification(email: "user@example.com")

// Check verification status
let user: JSONRecord = try await client
    .collection("users")
    .getOne("user-id")

if let verified = user["verified"]?.value as? Bool, verified {
    print("User is verified")
}

2. Password Security

Never store passwords in plain text. BosBase handles password hashing automatically:

// ✅ Correct: Password is hashed automatically
let user: JSONRecord = try await client
    .collection("users")
    .create(body: [
        "email": AnyCodable("user@example.com"),
        "password": AnyCodable("secure-password")
    ])

// ❌ Wrong: Don't try to set passwordHash directly

3. Access Control

Use API rules to enforce access control:

// Users can only access their own data
options["listRule"] = AnyCodable("id = @request.auth.id")
options["viewRule"] = AnyCodable("id = @request.auth.id")
options["updateRule"] = AnyCodable("id = @request.auth.id")

4. Token Management

Tokens are automatically managed by authStore:

// Token is stored automatically after authentication
let authResult: AuthResult = try await client
    .collection("users")
    .authWithPassword(identity: "user@example.com", password: "password")

// Refresh token when needed
try await client.collection("users").authRefresh()

// Clear authentication
client.authStore.clear()

5. Error Handling

Always handle authentication errors:

do {
    let authResult = try await client
        .collection("users")
        .authWithPassword(identity: "user@example.com", password: "password")
} catch let error as ClientResponseError {
    if error.status == 400 {
        print("Invalid credentials")
    } else if error.status == 404 {
        print("User not found")
    }
}

Complete Example: User Management System

import BosBase

let client = try BosBaseClient(baseURLString: "http://127.0.0.1:8090")

// 1. Register a new user
let newUser: JSONRecord = try await client
    .collection("users")
    .create(body: [
        "email": AnyCodable("user@example.com"),
        "password": AnyCodable("password123"),
        "passwordConfirm": AnyCodable("password123"),
        "name": AnyCodable("John Doe")
    ])

print("User registered: \(newUser["id"]?.value ?? "")")

// 2. Request email verification
try await client
    .collection("users")
    .requestVerification(email: "user@example.com")

// 3. User logs in
let authResult: AuthResult = try await client
    .collection("users")
    .authWithPassword(identity: "user@example.com", password: "password123")

print("Authenticated: \(authResult.record["email"]?.value ?? "")")

// 4. Update user profile
let updated: JSONRecord = try await client
    .collection("users")
    .update(authResult.record["id"]?.value as? String ?? "", body: [
        "name": AnyCodable("John Updated"),
        "bio": AnyCodable("This is my bio")
    ])

// 5. Get current user
let currentUser: JSONRecord = try await client
    .collection("users")
    .getOne(authResult.record["id"]?.value as? String ?? "")

print("Current user: \(currentUser["name"]?.value ?? "")")

// 6. Create user-specific data
let post: JSONRecord = try await client
    .collection("posts")
    .create(body: [
        "title": AnyCodable("My First Post"),
        "content": AnyCodable("Post content"),
        "author": AnyCodable(authResult.record["id"]?.value as? String ?? "")
    ])

// 7. Get user's posts
let userPosts: ListResult<JSONRecord> = try await client
    .collection("posts")
    .getList(
        filter: "author.id = '\(authResult.record["id"]?.value as? String ?? "")'",
        expand: "author"
    )

print("User has \(userPosts.totalItems) posts")

For more information, see: