Skip to content

Authentication

ARROW uses Zitadel as its centralized identity and access management (IAM) platform. Zitadel provides single sign-on (SSO), user lifecycle management, and multi-tenancy support through organization mapping.

ARROW integrates with Zitadel using a service account for automated user management:

graph TD
    A[ARROW Frontend] -->|SSO Login| B[Zitadel]
    C[ARROW Backend] -->|Service Account| B
    B -->|OAuth Tokens| A
    C -->|User Management| B
    D[Organizations] -->|Mapped to| E[Zitadel Orgs]
    C -->|Project Access| F[Arrow Project]
    C -->|VPN Access| G[NetBird Project]
ComponentIntegration
User AuthenticationOAuth 2.0 / OIDC SSO via Zitadel
User ProvisioningAutomated user creation in Zitadel
Organization MappingARROW orgs map to Zitadel orgs
Project AccessUsers added to Arrow and NetBird projects
Status SyncBidirectional active/inactive sync

Zitadel integration is configured through the system_integrations collection with type: "zitadel":

{
"type": "zitadel",
"domain": "your-instance.zitadel.cloud",
"client_id": "application-client-id",
"client_secret": "application-client-secret",
"project_id": "arrow-project-id",
"service_account_json": "{...}"
}
FieldDescription
domainZitadel instance domain (e.g., your-instance.zitadel.cloud)
client_idOAuth client ID for the ARROW application
client_secretOAuth client secret
project_idZitadel project ID for ARROW application
service_account_jsonService account credentials for API access

The service account JSON follows Zitadel’s JWT Profile format:

{
"type": "serviceaccount",
"keyId": "key-id-from-zitadel",
"key": "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----\n",
"userId": "service-account-user-id"
}
FieldDescription
typeMust be serviceaccount
keyIdKey identifier from Zitadel
keyRSA private key in PEM format
userIdService account user ID
  1. Navigate to Zitadel Console > Service Users
  2. Create a new service user with Manager role
  3. Generate a new key (JWT Profile)
  4. Download the JSON key file
  5. Store the JSON in ARROW’s service_account_json field

ARROW automatically manages users in Zitadel when users are created, updated, or deleted.

When a user is added to ARROW, the handleUserCreationWithZitadel function (lines 351-756 in backend/hooks/user_permissions.go) orchestrates the following:

sequenceDiagram
    participant Admin
    participant Arrow
    participant Zitadel
    participant NetBird

    Admin->>Arrow: Create User
    Arrow->>Zitadel: Get Service Account Token
    Zitadel-->>Arrow: Access Token
    Arrow->>Zitadel: Create User in Org Context
    Zitadel-->>Arrow: User Created (userId)
    Arrow->>Zitadel: Add to Arrow Project
    Arrow->>Zitadel: Add to NetBird Project
    Arrow->>Arrow: Sync User Permissions
    Arrow->>NetBird: Create VPN User
    Arrow-->>Admin: User Created Successfully
  1. Acquire service account token - JWT assertion grant to Zitadel
  2. Switch organization context - Use zitadel_org_id from ARROW organization
  3. Create user in Zitadel - With email and profile information
  4. Assign project memberships - Add to Arrow project and NetBird project
  5. Trigger email verification - User receives verification email
  6. Sync permissions - Aggregate role permissions to user record
  7. Create VPN user - Provision NetBird user account

ARROW organizations map to Zitadel organizations via the zitadel_org_id field:

ARROW FieldZitadel Mapping
organizations.zitadel_org_idZitadel organization ID
organizations.netbird_project_idNetBird project for VPN access

When creating users, ARROW switches to the appropriate Zitadel organization context to ensure proper multi-tenancy isolation.

ARROW maintains bidirectional sync of user active/inactive status with Zitadel.

A cron job runs every 30 minutes (configured in backend/api/zitadel/handlers.go, lines 20-36) to synchronize status:

scheduler.Every(30).Minutes().Do(func() {
SyncUserStatusFromZitadel(app)
})
Zitadel StatusARROW Action
ActiveEnsure ARROW user is active
InactiveMark ARROW user as inactive
DeletedHandle as inactive

Administrators can trigger immediate status sync:

POST /api/zitadel/users/sync-status

ARROW provides endpoints to manage user status:

EndpointAction
POST /api/zitadel/users/{id}/activateActivate user in Zitadel
POST /api/zitadel/users/{id}/deactivateDeactivate user in Zitadel

ARROW supports multi-tenancy by mapping organizations to Zitadel organizations.

Each ARROW organization can be linked to a Zitadel organization:

FieldDescription
zitadel_org_idZitadel organization identifier
netbird_project_idNetBird project for VPN access
EndpointDescription
GET /api/zitadel/organizationsList available Zitadel organizations
GET /api/zitadel/org-mappingGet current ARROW-Zitadel mappings
POST /api/zitadel/org-mappingUpdate organization mapping

Organization mapping provides:

  • Isolated user pools - Users belong to specific Zitadel organizations
  • Separate billing - Organization-level Zitadel subscriptions
  • Custom branding - Per-organization login pages
  • Independent admin - Organization admins manage their own users

ARROW manages access to multiple Zitadel projects for different functionality.

The main ARROW application project:

PurposeProvides
Application accessLogin to ARROW portal
Role-based accessAdmin, Manager, User roles
API accessAuthenticated API calls

Per-organization VPN access project:

PurposeProvides
VPN authenticationNetBird VPN login
Device accessConnect to ARROW-managed devices
Access policiesConsultant-device access control
EndpointDescription
POST /api/zitadel/projects/{id}/membersAdd user to project
DELETE /api/zitadel/projects/{id}/members/{userId}Remove user from project

Separate application for management functions:

FeatureDescription
Site administrationCross-organization management
System configurationGlobal settings
Integration managementZitadel/NetBird configuration

ARROW uses OAuth 2.0 tokens for API authentication with Zitadel.

Service account tokens are acquired using JWT Profile assertion:

sequenceDiagram
    participant Arrow
    participant Zitadel

    Arrow->>Arrow: Create JWT Assertion
    Arrow->>Zitadel: POST /oauth/v2/token
    Note over Arrow,Zitadel: grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
    Zitadel-->>Arrow: Access Token + Expiry
    Arrow->>Arrow: Cache Token

The JWT assertion includes:

ClaimValue
issService account user ID
subService account user ID
audZitadel issuer URL
iatCurrent timestamp
expExpiration (typically 1 hour)

ARROW validates tokens through Zitadel’s introspection endpoint:

POST /oauth/v2/introspect

Token endpoints are rate-limited to prevent abuse:

LimitValue
Requests per second3
Burst5

ARROW caches access tokens to minimize API calls:

  1. Store token with expiry - Cache token until near expiration
  2. Refresh proactively - Request new token before expiry
  3. Handle failures gracefully - Retry with exponential backoff
  • Store service account JSON securely (environment variable or secrets manager)
  • Rotate keys periodically
  • Use minimum required permissions
  • Monitor service account activity
  • Never log access tokens
  • Use HTTPS for all token exchanges
  • Validate token signatures
  • Check token expiration
  • Verify organization context for all operations
  • Prevent cross-organization data access
  • Audit organization switching events
FilePurpose
backend/hooks/user_permissions.goUser creation with Zitadel (lines 351-756)
backend/api/zitadel/handlers.goStatus sync and API endpoints (lines 20-36)
backend/api/zitadel/client.goZitadel API client
CollectionRelevant Fields
system_integrationstype, domain, client_id, project_id, service_account_json
organizationszitadel_org_id, netbird_project_id
userszitadel_user_id, email, active