Skip to main content

Permissions in Practice

This tutorial builds a realistic permissions setup from scratch: two users (alice, project lead; bob, read-only analyst), an Analytics project, a full-access and a read-only policy, and project-scoped API keys for each — then verifies what each key can and cannot do.

Prerequisites

  • SOAT running locally. Follow the Quick Start guide to bring the stack up with Docker Compose.
  • New to SOAT? Read Key Concepts to understand projects, users, and the IAM model before diving in.
  • CLI installed and configured, or SDK set up. See CLI or SDK.
  • For production hardening (secrets, env vars), see Configuration.
  • Server is at http://localhost:5047.

Export your server URL (used in subsequent steps):

export SOAT_BASE_URL=http://localhost:5047

CLI path flags in this tutorial are resource-specific and kebab-cased, for example --user-id and --project-id.


Step 1 — Log in as admin

Admin is the built-in superuser role. It bypasses policy evaluation entirely. See IAM — Authentication for details on JWT tokens and the admin role.

soat login-user --username admin --password Admin1234!

The CLI prints a token. Save it and update your profile:

soat configure
# Token: <paste token here>

Step 2 — Create regular users

Create alice (project lead) and bob (read-only analyst). Only admins can create users. See Users for the full user management reference.

ALICE_ID=$(soat create-user --username alice --password Alice1234! | jq -r '.id')
BOB_ID=$(soat create-user --username bob --password Bob1234! | jq -r '.id')
echo "alice: $ALICE_ID"
echo "bob : $BOB_ID"

Note the id field (user_…) for each user — you will need them when attaching policies.


Step 3 — Create the Analytics project

See Projects for the full project management reference.

PROJECT_ID=$(soat create-project --name "Analytics" | jq -r '.id')
echo "project: $PROJECT_ID"

Copy the returned id (e.g. proj_…).


Step 4 — Create policies

You will create two policies. Replace $PROJECT_ID with the actual value from the previous step.

Policies are global (not scoped to any project). The resource field uses SOAT Resource Names (SRNs) to restrict which projects a policy covers. For the full policy document format and evaluation rules, see IAM — Policy Documents.

4a — Full-access policy (for Alice)

This policy allows all actions on every resource inside the Analytics project.

FULL_POLICY_ID=$(soat create-policy \
--name "analytics-full-access" \
--description "Full access to the Analytics project" \
--document '{
"statement": [
{
"effect": "Allow",
"action": ["*"],
"resource": ["srn:'"$PROJECT_ID"':*:*"]
}
]
}' | jq -r '.id')
echo "full-access policy: $FULL_POLICY_ID"

4b — Read-only policy (for Bob)

This policy only allows read actions on files inside the project. For the full list of files:* and documents:* actions, see the Permissions Reference.

READ_POLICY_ID=$(soat create-policy \
--name "analytics-read-only" \
--description "Read-only access to files and documents in Analytics" \
--document '{
"statement": [
{
"effect": "Allow",
"action": [
"files:GetFile",
"documents:ListDocuments",
"documents:GetDocument"
],
"resource": ["srn:'"$PROJECT_ID"':*:*"]
}
]
}' | jq -r '.id')
echo "read-only policy: $READ_POLICY_ID"

Step 5 — Attach policies to users

Attach the full-access policy to Alice and the read-only policy to Bob. See Policies — Attaching Policies to Users for more details.

note

PUT /users/:user_id/policies replaces the user's entire policy list with the provided array.

# Give Alice full access
soat attach-user-policies \
--user-id "$ALICE_ID" \
--policy-ids '["'"$FULL_POLICY_ID"'"]'

# Give Bob read-only access
soat attach-user-policies \
--user-id "$BOB_ID" \
--policy-ids '["'"$READ_POLICY_ID"'"]'

Step 6 — Create API keys

API keys allow programmatic access without sending a username and password. See API Keys for key rotation and revocation. Here you create two keys:

  • Alice's key — scoped to the Analytics project, inherits her full-access policy.
  • Bob's key — scoped to the Analytics project, further restricted to the read-only policy.

First, log in as each user to obtain their JWT tokens.

# Log in as Alice and save her token to a named profile
soat login-user --username alice --password Alice1234!
soat configure --profile alice

# Log in as Bob and save his token
soat login-user --username bob --password Bob1234!
soat configure --profile bob

# Create Alice's API key (using her profile)
ALICE_API_KEY=$(soat --profile alice create-api-key \
--name "alice-analytics-key" \
--project-id "$PROJECT_ID" | jq -r '.key')

# Create Bob's API key, explicitly restricting it to the read-only policy
BOB_API_KEY=$(soat --profile bob create-api-key \
--name "bob-analytics-key" \
--project-id "$PROJECT_ID" \
--policy-ids '["'"$READ_POLICY_ID"'"]' | jq -r '.key')

echo "Alice key: $ALICE_API_KEY"
echo "Bob key : $BOB_API_KEY"
warning

The raw sk_… key is returned only once — store it in a secret manager immediately. If lost, delete the key and create a new one.


Step 7 — Verify permissions

Confirm that each key behaves as expected. The file upload and list operations used here are part of the Files module.

Alice can upload a file

echo "hello world" > sample.txt

SOAT_TOKEN="$ALICE_API_KEY" soat upload-file-base64 \
--project-id "$PROJECT_ID" \
--content "$(base64 -w 0 sample.txt)" \
--filename "sample.txt"

# Switch to Bob's profile to test his permissions
# → expect-fail
SOAT_TOKEN="$BOB_API_KEY" soat upload-file-base64 \
--project-id "$PROJECT_ID" \
--content "$(base64 -w 0 sample.txt)" \
--filename "sample.txt"

Bob can read files

# Bob can list files — read is allowed
SOAT_TOKEN="$BOB_API_KEY" soat list-files --project-id "$PROJECT_ID"

Bob's key cannot exceed Bob's own permissions

Even if you tried to assign FULL_POLICY_ID to Bob's API key, it would not grant more than what Bob's user policies already allow. The effective permissions are always the intersection. See IAM — Authorization Model for the full rules.

# Attempt to create a key for Bob with the full-access policy
# Key is created, but when used it is still limited to Bob's read-only permissions
# because Bob's user policies are the ceiling.
ESCALATED_KEY=$(soat --profile bob create-api-key \
--name "bob-escalation-attempt" \
--project-id "$PROJECT_ID" \
--policy-ids '["'"$FULL_POLICY_ID"'"]' | jq -r '.key')

# → expect-fail
SOAT_TOKEN="$ESCALATED_KEY" soat upload-file-base64 \
--project-id "$PROJECT_ID" \
--content "$(base64 -w 0 sample.txt)" \
--filename "sample.txt"

Next steps

  • Explore IAM for the full policy document format including wildcard actions and SRN patterns.
  • See Policies for the complete policy CRUD API.
  • See API Keys for key rotation and revocation patterns.