Building a Custom SCIM Server in Okta

Building a Custom SCIM Server in Okta

A friendly, ground-up guide to creating your own SCIM service so that Okta can create, update, and remove people and groups inside an application that has no ready-made connector. You will learn what SCIM is, what Okta sends, what your server must answer, and how to design, secure, test, and run it.

Imagine you run a large library, and every September hundreds of new students join. A clerk at the front desk writes each name in a book, hands out a card, and gives access to the right rooms. In June, when students leave, the same clerk crosses their names out and takes the cards back. Now imagine you have fifty libraries in different towns, each with its own book, its own card style, and its own way of crossing names out. The clerk would need fifty different habits. Life would be easier if every library agreed on one standard form for “add a student,” “change a student,” and “remove a student.”

SCIM is that standard form for the software world. Okta is the clerk at the front desk of your company. Each business application is one library. When a new employee joins, Okta wants to tell every app, in one common language, “Please create this person.” When the employee leaves, Okta wants to say, “Please switch this person off.” Many popular apps already speak this language and have a ready-made connector. Many others, especially your own in-house applications, do not. For those, you build a small service that listens for Okta’s requests and does the right thing inside your app. That service is a custom SCIM server.

This guide teaches you how to build one, in words and pictures, with no code. You do not need any prior knowledge. Every new term is explained when it first appears, and every big idea comes with an everyday comparison. By the end, you will understand what Okta sends, what your server must answer, how to store and match people, how to keep the service safe and fast, and how to find the cause when something goes wrong.

!
Before you start

This guide describes ideas and typical behavior in plain words rather than exact code or exact screen names. Okta changes its products over time, and details such as which requests are sent, which authentication choices exist, and which settings appear can vary by release, license, and integration type. Always confirm exact requirements in the official Okta provisioning documentation and in the SCIM standards before you build or go live.

1Why Build a Custom SCIM Server at All?

Before learning how, it helps to understand why anyone would take on this work.

Every company has a long list of people who should have access to its applications. That list changes every day. People join, change teams, change names, take leave, and leave for good. If someone updates each application by hand, three bad things happen. Work is slow, mistakes creep in, and former employees keep access long after they should have lost it. That last problem is a real security risk.

The problem a custom server solves

Okta can keep applications up to date automatically. This is called provisioning. For popular products such as large email and chat tools, Okta already has a built-in connector in its catalog. You switch it on and it works. But your company may also have a homegrown claims system, an old internal portal, a partner extranet, or a new product you are building. Okta has no ready-made connector for these, because Okta has never seen them.

You have two choices. You can keep updating the app by hand, or you can give the app a front door that speaks the standard language Okta already understands. Building that front door is what this guide is about.

Why the standard language is a gift

If you invented your own private language, you would have to teach it to every tool that needs to talk to your app. Because SCIM is a public standard, you only teach your app once. Okta speaks it, and many other identity tools do too. The same server that works for Okta today could work with another identity product tomorrow.

Everyday analogy

Think of the electric plug in your wall. Every lamp, fan, and phone charger uses the same plug shape, so you can move them from room to room. A custom SCIM server is a plug socket you install on your own app so that Okta’s standard plug fits. You do not rebuild the lamp. You only fit the socket.

1
standard language for many apps
0
hand-edited user lists needed
Fast
removal of access at departure

When it is the wrong answer

A custom server is not always the best choice. If the app already has a catalog connector, use it. If the app has a different supported way to receive users, such as a file import or a vendor-provided connector, that may be simpler. If only five people ever use the app and they rarely change, hand updates may be cheaper than building and running a service. Later chapters give a decision guide to help you choose.

i
Beginner tip

Ask one question first: “Does Okta already have a way to talk to this app?” Only when the answer is no does a custom server become worth its cost.

2Core Concepts You Need First

A small set of words appears again and again in this topic. Learn them once and the rest reads easily.

Identity

An identity is the digital record of a person or a thing that needs access. It holds facts such as a name, an email address, a department, and a status. Think of an identity as a name tag with extra details written on the back.

Provisioning and deprovisioning

Provisioning means creating and keeping accounts up to date in applications. Deprovisioning means switching accounts off or removing them when they are no longer needed. Together they are the “joiner, mover, leaver” story of every employee. Joiner means a new person arrives. Mover means they change roles. Leaver means they depart.

Identity provider and service provider

In this story, Okta is the identity provider. It is the trusted source of truth about people. Your application is the service provider. It consumes accounts and uses them to decide what each person can do. In SCIM language, Okta plays the role of the client, which sends requests, and your server plays the role of the service provider, which answers them.

SCIM

SCIM stands for System for Cross-domain Identity Management. It is a pair of public standards that describe how to represent users and groups, and how to create, read, change, and delete them over the web. It is built on ordinary web calls and a common data format called JSON, which is simply a tidy way of writing facts as labeled pairs. There are two versions, 1.1 and 2.0, and 2.0 is the one most modern systems use.

Resource

A resource is a thing SCIM can manage. The two everyday resources are users and groups. Each resource has a unique identifier, a set of attributes, and some bookkeeping information such as when it was created.

Schema

A schema is a description of what a resource looks like: its allowed attributes, the type of each one, and whether it is required. Think of a schema as the printed layout of a form. It says which boxes exist and what kind of thing belongs in each.

Endpoint

An endpoint is a web address where your server listens for one kind of request. There is typically one for users, one for groups, and a few that describe the server itself.

Idempotent

An action is idempotent when doing it twice has the same result as doing it once. Switching a lamp to “off” is idempotent. Pressing a “toggle” button is not. Good provisioning servers aim to be idempotent wherever possible, because networks can repeat requests.

TermPlain meaningEveryday comparison
ProvisioningCreating and updating accountsHanding out and updating library cards
DeprovisioningSwitching off or removing accountsCollecting the card at the end of the year
SCIMStandard way to manage users and groupsA common form every library accepts
ResourceA managed thing such as a userOne page in the book
SchemaThe layout of a resourceThe printed boxes on the form
EndpointAddress that handles one request typeA desk for one kind of task
IdempotentSafe to repeatSetting a switch to off
Everyday analogy

Think of a hotel. The booking company is Okta, keeping the master list of guests. The hotel is your app, which must actually make rooms ready. SCIM is the standard booking form both sides agree on, so any booking company can reserve a room at any hotel.

3How Okta Fits In: The Client and the Server

The most important idea in this guide is who talks and who listens. Okta talks. Your server listens and answers.

Okta is the caller

Your server never decides on its own to create a person. It waits. When something happens in Okta, such as assigning a person to an application, Okta sends a request to your server. Your server does the work and replies with the result. This means you do not have to teach your server about Okta’s inner workings. You only have to teach it to answer standard SCIM requests correctly.

Where the connection is configured

In Okta’s Admin Console, you create an application integration for your app, then switch on provisioning for it. You tell Okta the base address of your SCIM server, how to authenticate to it, and which actions you want Okta to perform, such as creating users, updating attributes, and deactivating users. The exact path through the console, and the options offered, depend on your Okta release and license, so confirm them in the documentation.

The cast of characters

Source

Okta Universal Directory

Holds the master record of every person and their facts.

Client

Okta provisioning engine

Notices changes and sends SCIM requests for them.

Network

Secure web connection

Carries requests over encrypted web traffic.

Server

Your SCIM service

Receives requests, checks them, and talks to your app.

Target

Your application

Owns the real accounts and decides what people can do.

Record

Logs on both sides

Show what was asked, what was answered, and when.

flowchart LR
    HR["HR system
source of truth"] --> OKTA["Okta
Universal Directory"] OKTA -->|"Assignment or change"| ENG["Okta provisioning engine"] ENG -->|"SCIM requests over HTTPS"| SRV["Your custom SCIM server"] SRV -->|"Create, update, switch off"| APP["Your application and its data"] SRV -->|"Replies with results"| ENG
Figure 1 · Okta asks, your SCIM server translates, your application acts
Everyday analogy

Think of a translator at an international meeting. The speaker (Okta) talks in a standard language. The translator (your SCIM server) turns it into the local language of the listener (your app) and carries the answer back. The translator never starts the conversation. They only respond to what is said.

i
Key idea

Your server’s job is translation and honesty. Translate the standard request into your app’s actions, and report truthfully what happened.

4The SCIM Protocol in Plain Words

SCIM looks big in its standards, but its daily use is a small set of ordinary web actions.

Four everyday actions

SCIM is built on the ordinary web. Each request has a verb that says what the caller wants and an address that says which thing it is about. The common verbs are these.

  • Get asks to read something, such as one user or a list of users.
  • Post asks to create something new.
  • Put asks to replace a whole record with a new version.
  • Patch asks to change only some parts of a record.
  • Delete asks to remove a record.

The common addresses

A SCIM server has a base address. After it, standard names identify what is being managed. One name leads to users, another to groups. Additional names describe the server itself: what it supports, what resource types it offers, and what attribute layouts it uses. Those descriptive endpoints let a caller discover capabilities without guessing.

The shape of the data

Facts travel as JSON, a plain-text format of labeled values. A user record has an identifier chosen by your server, a user name that people sign in with, a name block, email addresses, an active flag, and more. Every record also carries a list of schema labels that announce which layouts it follows, and a meta block with created and modified times.

How replies communicate

Every reply carries a status number. Numbers beginning with two mean success. For example, one number means “found and here it is,” another means “created,” and another means “done, nothing to return.” Numbers beginning with four mean the caller made a mistake, and numbers beginning with five mean your server failed. Chapter 12 goes through the important ones.

sequenceDiagram
    participant O as Okta
    participant S as Your SCIM server
    participant A as Your application
    O->>S: Ask for the user with this user name
    S->>A: Look up the account
    A-->>S: Not found
    S-->>O: Empty list of results
    O->>S: Create this user
    S->>A: Make the account
    A-->>S: Account created with an identifier
    S-->>O: Created, here is the user with its identifier
    
Figure 2 · A typical first conversation: look, then create
Everyday analogy

Think of ordering at a counter. You say what you want (the verb), you point at the menu item (the address), you hand over details (the data), and the cook replies with “ready,” “sold out,” or “sorry, the kitchen is on fire” (the status). SCIM is just a polite, standard way of placing the order.

i
Beginner tip

If you can read a menu and understand a receipt, you can understand SCIM. Do not be put off by the standards documents. Most of your work uses a small slice of them.

5The User Resource: What a Person Looks Like

The user record is where most of your effort goes. Understanding its parts will save you hours later.

The identifier

Each user has an identifier created by your server, not by Okta. It must be unique, stable, and never reused. Okta stores it and uses it in later requests to find the same person again. Think of it as the number on the library card. A person’s name might change, but the card number stays.

The user name

The user name is the human-friendly handle, often an email address. It must be unique in your app, and Okta relies on it to check whether a person already exists. Because the first question Okta usually asks is “does this user name already exist,” your server must search by user name quickly and accurately.

Names, emails, and the active flag

The name block holds given name, family name, and sometimes a display name. Emails are a list, where each entry has a value, a type such as work, and an optional primary marker. The active flag is a simple true or false that says whether the account is enabled. This tiny flag matters enormously: it is how Okta switches a leaver off without destroying their history.

Schemas and extensions

The core user layout covers common facts. Real companies also need department, manager, cost center, and so on. The standard provides an enterprise extension for many of these. For facts unique to your app, such as a clearance level, you can define a custom extension with its own schema label. Okta can map its own attributes to these, with your choices made in the application’s provisioning settings.

Meta information

Every record carries a meta block: the resource type, when it was created, when it was last modified, and a version tag. Good servers fill these in correctly. They help callers tell whether their copy is out of date.

Part of a userWho sets itWhy it matters
IdentifierYour serverLets Okta find the same person again
User nameOkta, from its mappingUsed to check whether a person already exists
Name and emailsOkta, from its mappingShown to people and used for contact
Active flagOktaEnables or disables the account
Extension factsOkta, if mappedCarry department, manager, and app-specific data
Meta blockYour serverTracks creation, change, and version
flowchart TD
    U["User resource"] --> ID["Identifier
set by your server"] U --> UN["User name
unique handle"] U --> NM["Name block"] U --> EM["Emails list"] U --> AC["Active flag"] U --> EX["Extensions
department, manager, custom"] U --> MT["Meta
times and version"]
Figure 3 · The parts of a SCIM user record
Everyday analogy

Think of a passport. It has a number that never changes (identifier), a name you are known by (user name), personal details, a stamp saying whether it is valid (active flag), and extra pages for visas (extensions). Each part has a distinct job.

!
Common trap

Never use a person’s email address as the identifier. People change emails. The identifier must outlive every other fact about the person.

6The Group Resource: Collections of People

Groups let Okta manage access for many people at once.

What a group is

A group is a named collection with an identifier, a display name, and a list of members. Each member entry points to a user by identifier. If your application uses groups, teams, or roles to decide what people can do, Okta can send them to your server so that membership changes in Okta flow into your app.

Why groups are optional

Not every app needs group support. If your app only needs to know “is this person allowed in,” user provisioning is enough. If your app has roles such as viewer, editor, and administrator, group support lets Okta assign people to roles without anyone touching your app. Okta’s feature for sending groups is commonly called pushing groups, and it is usually enabled separately from user provisioning. Confirm the availability for your integration type.

Membership changes

Group updates usually arrive as changes to the member list: add these people, remove those people. These are typically sent as patch requests that touch only the members, so your server must understand partial changes instead of expecting a whole replacement.

Groups as roles in your app

A good design decision is to treat each group as a role in your application. The group display name maps to a role name, and membership maps to who holds that role. Keeping this mapping simple makes both the server and the Okta configuration easy to understand.

Everyday analogy

Think of a school class list. Rather than telling the cafeteria about each child one by one, you hand over “Class 5B” and say who is in it. When a child joins or leaves, you only update the list. Groups do the same for access.

i
Design hint

Start with users only. Add groups later, once the user flow is stable. Each feature you add is one more thing to test and secure.

7The Account Lifecycle: What Okta Asks Your Server to Do

Every account in your app goes through a life: it is born, it changes, it rests, and it is retired. Okta can drive each stage.

Create

When a person is assigned to your application in Okta, Okta decides your app needs an account for them. It typically first asks whether the user name already exists. If not, it sends a request to create the user. If your server says the user already exists, Okta may link to the existing account instead, depending on how the integration is configured. This “look first, then create” habit protects you from duplicates.

Update

When facts change in Okta, such as a new department or a changed surname, Okta sends an update so your app stays in step. Updates may be sent as whole replacements or as partial changes. Your server should be ready for both, because Okta’s behavior can depend on the SCIM version and the settings of the integration.

Deactivate

When a person is unassigned from the app, or their Okta account is suspended or deactivated, Okta tells your server to switch the account off. This usually means setting the active flag to false. It does not normally mean deleting the record. Keeping the record preserves history, ownership of documents, and audit trails.

Reactivate

If the person returns, Okta can switch the account back on by setting the active flag to true again. Your server must support this cleanly, restoring the same account rather than creating a new one.

Import

Sometimes the flow runs the other way. Okta can read the list of users that already exist in your application so it can match them with people in Okta. This helps when an app has years of accounts that were created before Okta was introduced. Import requires your server to list users with paging, covered in Chapter 9.

Delete

Okta commonly prefers switching accounts off over deleting them. Some integrations can be set up to delete, and a server should still handle a delete request gracefully, but you should plan on the active flag being the main off-switch. Confirm in the Okta documentation how your integration treats removal.

1

Assign

An administrator or a rule assigns the person to your app in Okta.

2

Look up

Okta asks your server if this user name already exists.

3

Create or link

If new, your server creates the account. If it exists, Okta links to it.

4

Keep in step

Changes in Okta are sent as updates and applied by your server.

5

Switch off

When access ends, Okta sets the active flag to false.

6

Bring back

If the person returns, the same account is switched on again.

stateDiagram-v2
    [*] --> NotInApp
    NotInApp --> Active : "Create"
    Active --> Active : "Update facts"
    Active --> Inactive : "Deactivate"
    Inactive --> Active : "Reactivate"
    Inactive --> Removed : "Delete, if used"
    Removed --> [*]
    
Figure 4 · The life of an account as Okta drives it
Everyday analogy

Think of a phone line. A new person gets a number (create). They may change their plan (update). When they travel abroad for a year, the line is paused (deactivate), and it can be resumed later (reactivate). Only rarely is the number given away for good (delete). Okta treats accounts the same careful way.

!
Do not skip this

The “switch off” path is the most important one for security. A server that creates beautifully but fails to disable correctly leaves former employees with working accounts.

8The Endpoints Your Server Should Offer

Think of your server as a building with a few desks. Each desk handles one kind of request.

The users desk

This is the busiest desk. It must support searching for a user by user name, creating a user, reading one user by identifier, replacing or partially changing a user, and, ideally, removing one. It must also be able to list users with paging for imports.

The groups desk

Only needed if you support groups. It handles creating groups, reading them, changing their members, and removing them. Members are referenced by user identifier.

The descriptive desks

The standard also describes endpoints that tell callers about the server. One lists the features you support, such as whether you accept partial changes, filtering, and paging, and which sign-in methods you accept. Another lists the types of resource you offer. Another lists the attribute layouts. Not every caller reads these, and Okta’s behavior around them can depend on the integration, but providing them makes your server easier to test and adopt, and is part of being a good SCIM citizen.

DeskWhat it handlesNeeded for Okta?
Users: searchFind by user name or list with pagingYes, for existence checks and imports
Users: createMake a new accountYes, for onboarding
Users: read oneReturn an account by identifierYes, commonly
Users: updateReplace or partly change an accountYes, for keeping in step
Users: switch offSet the active flag to falseYes, essential
GroupsCreate groups and change membersOnly if you push groups
Service descriptionDeclare supported featuresRecommended
Resource types and schemasDeclare layoutsRecommended
flowchart TD
    BASE["SCIM base address"] --> USERS["Users desk"]
    BASE --> GROUPS["Groups desk"]
    BASE --> DESC["Descriptive desks"]
    USERS --> U1["Search"]
    USERS --> U2["Create"]
    USERS --> U3["Read"]
    USERS --> U4["Update"]
    GROUPS --> G1["Create"]
    GROUPS --> G2["Change members"]
    DESC --> D1["Service features"]
    DESC --> D2["Resource types"]
    DESC --> D3["Schemas"]
    
Figure 5 · The desks in the front hall of a SCIM server
Everyday analogy

Think of a post office with separate windows for parcels, letters, and enquiries. A visitor who goes to the right window gets quick service. A visitor who finds only one window for everything gets confused. Clear, separate desks make your server predictable.

i
Practical advice

Build the users desk first and test it thoroughly. Add the descriptive desks next, because they are cheap and helpful. Add groups last.

9Searching, Filtering, and Paging

Finding the right person quickly, and listing thousands of people politely, are two of the most important skills of a SCIM server.

Why searching matters

Before creating anybody, Okta typically asks whether a user with a given user name already exists. This question is asked with a filter, a short condition saying “user name equals this value”. If your server cannot answer it correctly, Okta cannot protect you from duplicates. At the least, support the “equals” test on the user name, and ideally on a few other attributes.

Case, spaces, and exactness

User names are often email addresses, and email addresses are usually treated without regard to capital letters. Decide whether your user name comparison is case sensitive, and be consistent. The standard says that for the user name, comparison is case insensitive. A search for “Asha@example.com” and “asha@example.com” should find the same person. Remember to trim stray spaces before comparing as well.

What a search reply looks like

A search returns a list wrapper: a total count of matches, the starting position, the number of items in this page, and the list of resources. An empty result is not an error. It is a successful reply with a total of zero. Okta reads that as “no such person, go ahead and create.”

Why paging exists

If your app has fifty thousand users, you cannot return them in one reply. The reply would be huge and slow, and could crash the caller. Paging solves this. The caller asks for a number of items starting from a position, and your server returns just that slice. The caller then asks for the next slice, and so on. SCIM numbers positions starting at one, not zero. This difference between the standard and many programming habits is a frequent source of off-by-one mistakes.

Keeping order stable

Paging works only if the order of results does not change between requests. If new users are inserted while someone is paging, items can be skipped or repeated. Sort by a stable key such as creation order or identifier, so each slice picks up exactly where the last one ended.

Protecting yourself

Set a maximum page size. If a caller asks for ten million items at once, return your maximum and tell them how many items you actually returned. Also be careful with filters: accept only the forms you support and reject the rest with a clear error, rather than risking slow or unsafe searches.

sequenceDiagram
    participant O as Okta
    participant S as Your SCIM server
    O->>S: List users, start at 1, up to 100
    S-->>O: 100 users, total 250
    O->>S: List users, start at 101, up to 100
    S-->>O: 100 users, total 250
    O->>S: List users, start at 201, up to 100
    S-->>O: 50 users, total 250
    Note over O,S: The caller stops when it has seen the total
    
Figure 6 · Paging through 250 users, one slice at a time
Everyday analogy

Think of reading a big book through a bookmark. You read a few pages, place the bookmark, close the book, and later open exactly where you stopped. Paging is the bookmark. If someone tears out pages between your visits, the bookmark no longer points to the right place, which is why stable order matters.

!
Off-by-one alert

SCIM positions start at one. If your server treats the first item as position zero, every page will be shifted and the first person on each page will be missed.

10Replace or Patch: Two Ways to Update

Updating a record can mean “here is the whole new version” or “change just these parts”. Your server must understand both ideas.

Replace

A replace request sends the complete user. Your server overwrites what it had with what it received. Anything the caller left out is treated as removed or reset. This is simple, but dangerous if the caller does not know about attributes that your server keeps. For example, if your app stores a local preference that Okta never sends, a naive replace could erase it. Decide in advance which attributes belong to Okta and which belong to your app, and leave the second group alone.

Patch

A patch request lists operations: add this value, remove that value, replace this attribute. It touches only what it names. This is efficient and safer, but it requires your server to understand the standard way of pointing at parts of a record, including paths into lists such as emails or group members.

What Okta tends to send

Okta can use replace for general profile updates and patch for specific tasks such as switching an account off or changing group membership, though exact behavior depends on SCIM version and integration. The safest approach is to support both on your users desk. A server that accepts only one will break in unexpected situations.

Idempotent updates

Whichever you support, an update should be safe to repeat. Setting the active flag to false twice must not fail the second time. Adding a member who is already in the group should not create a duplicate entry, and removing a member who is not in the group should not raise an alarm.

Reading what you wrote

After a successful update, return the full current record. Okta can use it to confirm what your server actually stored. If your server silently ignores an attribute it does not support, the returned record will show it, and the mismatch becomes visible rather than hidden.

Replace

Whole new version

Simple to understand. Risky if your app holds extra facts.

Patch

Targeted changes

Efficient and safer, but needs path handling.

Both

Support both

Okta may use either, depending on task and settings.

Safe

Repeatable

Applying the same change twice leaves the same result.

Everyday analogy

Think of editing a document. Replace is handing over a brand new copy and throwing away the old one. Patch is writing “change the date on page two” on a sticky note. If you only hand over new copies, careful notes your colleague added in the margin of the old one are lost.

i
Design hint

Write down which attributes Okta owns. Replace and patch should change only those. Everything else in your app belongs to your app.

11Authentication: Proving Okta Is Really Okta

Your SCIM server can create and disable accounts. That makes it one of the most sensitive doors in your system. It must never open for strangers.

Why this is so serious

Anyone who can talk to your SCIM server as if they were Okta can create an administrator account, or disable everyone. Protecting this door is not optional. Every request must prove who it comes from before anything happens.

The common choices

Okta can authenticate to a SCIM server in several ways, and the choices on offer depend on the integration type and release. Typically they include a secret token sent in a request header, sign-in with a username and password, and OAuth 2.0 where Okta first obtains a short-lived access token. Check the current list in your Okta console and documentation.

MethodHow it worksStrengthWeakness
Static bearer tokenA long secret is sent with each requestSimple to set upIf leaked, valid until changed
Username and passwordCredentials are sent with each requestFamiliarPasswords are often weak or reused
OAuth 2.0 tokenA short-lived token is obtained and renewedLimited lifetime, better controlMore to build and operate

Building a safe check

Your server should reject any request without valid credentials, using the standard “not authenticated” status, before doing any work. Compare secrets in a way that does not reveal how close a wrong guess was. Keep the secret out of logs. Store it in a proper secret vault, not in a file next to the code.

Rotation

Secrets should change on a schedule, and immediately if you suspect a leak. Design for rotation from the beginning: allow two valid secrets at once during a handover window, so you can change one side first without breaking provisioning.

Transport security

Always use encrypted web traffic. Okta expects a secure address for your SCIM base. Use a certificate from a trusted authority, and keep it renewed. An expired certificate can silently stop all provisioning.

sequenceDiagram
    participant O as Okta
    participant S as Your SCIM server
    participant V as Secret vault
    O->>S: Request with credentials
    S->>V: Fetch the expected secret
    V-->>S: Secret value
    alt Credentials match
        S-->>O: Process request and reply
    else Missing or wrong
        S-->>O: Not authenticated
    end
    
Figure 7 · Every request is checked before any work is done
Everyday analogy

Think of a night guard at the back door of a bank. The guard asks for the passcode before opening, even for people who say they are cleaners. The passcode is changed regularly, and the guard never writes it on the door. Your SCIM server must be that guard.

!
Never do this

Do not leave the SCIM address open to the whole internet without authentication “just for testing”. Test systems get forgotten and become real attack doors.

12Status Codes and Error Replies

When something goes wrong, your reply must tell Okta exactly what happened, so Okta can retry, give up, or show an administrator a useful message.

The success family

A successful read returns a “found” status with the record. A successful create returns a “created” status with the new record and its identifier. A successful removal returns “done with nothing to show”. Using the right success status matters, because Okta reads it to decide what to do next.

The caller-mistake family

When the request is wrong, reply with a status beginning with four. Bad data in the request means “invalid request”. A missing or wrong secret means “not authenticated”. Trying to create something that already exists means “conflict”. Asking for something that is not there means “not found”. Asking too often means “too many requests”. Each one tells the caller something different about what to do.

The server-mistake family

When your server itself fails, reply with a status beginning with five. This tells Okta the fault is on your side and that retrying later might help. Never use a server-error status for a caller mistake, and never use a caller-mistake status for your own crash. The caller will make the wrong decision.

The error body

SCIM defines a standard error body with a schema label, a status, and a human-readable detail message. Okta can show this detail to administrators in its logs. Write the detail message so that a tired administrator at midnight can understand it without opening your source code. Say which attribute was wrong and what was expected.

SituationReply meaningWhat Okta should do
User foundSuccess with recordUse the record
User createdCreated with recordRemember the identifier
No such user in searchSuccess with empty listProceed to create
Duplicate user nameConflictLink or report an administrator
Bad attribute valueInvalid requestReport; retry will not help
Wrong or missing secretNot authenticatedReport a connection problem
Too many requestsSlow downWait and retry
Your server crashedServer errorRetry later
flowchart TD
    R["Request arrives"] --> AU{"Authenticated?"}
    AU -->|"No"| E1["Reply: not authenticated"]
    AU -->|"Yes"| VA{"Request valid?"}
    VA -->|"No"| E2["Reply: invalid request with detail"]
    VA -->|"Yes"| EX{"Conflicts or missing?"}
    EX -->|"Duplicate"| E3["Reply: conflict"]
    EX -->|"Not there"| E4["Reply: not found"]
    EX -->|"Fine"| DO["Do the work"]
    DO --> OK{"Worked?"}
    OK -->|"Yes"| S1["Reply: success"]
    OK -->|"No"| E5["Reply: server error"]
    
Figure 8 · Choosing the right reply, step by step
Everyday analogy

Think of a vending machine. “Insert more coins” tells you what you did wrong. “Sold out” tells you the item is not there. “Out of service” tells you the machine is broken. Each message leads you to a different action. Your status codes should be as clear as those messages.

i
Admin-friendly errors

Include the attribute name and the reason in your error detail, such as “the department value is longer than the limit of fifty characters”. It will save hours of guessing.

13Architecture and Components of a SCIM Server

A good server is built in layers, each with one job. This makes it easier to test, fix, and grow.

The front door layer

This layer receives web requests. It checks the secure connection, confirms the caller’s credentials, and applies limits on how fast a caller may ask. It does not know anything about users. It only decides whether a request may come in.

The protocol layer

This layer understands SCIM. It reads the verb and address, decodes the data, checks that the request follows the standard layout, and builds standard replies and error bodies. It translates between the outside world’s SCIM language and your inside world’s plain operations.

The business layer

Here live your rules. Which user names are allowed? What happens when someone is switched off? Which attributes does Okta own? Which roles does a group map to? Keeping these rules in one place means you can change them without touching the protocol code.

The data layer

This layer talks to the database or to your application’s own interfaces. It creates, finds, updates, and disables accounts. It also stores the mapping between SCIM identifiers and your internal record numbers, if they differ.

The supporting parts

Around these layers sit logging, which records what happened; metrics, which count and time things; configuration, which holds settings and secrets; and a queue or retry helper, if some work has to happen later or in the background.

flowchart TD
    OK["Okta"] -->|"HTTPS"| FD["Front door
connection, credentials, limits"] FD --> PL["Protocol layer
SCIM parsing and replies"] PL --> BL["Business layer
rules and mapping"] BL --> DL["Data layer
database or app interfaces"] DL --> APP["Your application data"] FD -.-> LOG["Logging and metrics"] PL -.-> LOG BL -.-> LOG DL -.-> LOG CFG["Configuration and secrets"] -.-> FD CFG -.-> BL
Figure 9 · The layers of a well-built SCIM server
Layer 1

Front door

Decides who may enter and how fast.

Layer 2

Protocol

Speaks SCIM: parse, validate, reply.

Layer 3

Business

Holds your rules and attribute ownership.

Layer 4

Data

Reads and writes real accounts.

Everyday analogy

Think of a restaurant. The host at the door decides who comes in (front door). The waiter understands the menu language and writes the order (protocol). The head chef decides how dishes are made (business). The kitchen hands do the cooking (data). Each person has a job, and the restaurant runs smoothly because no one does everyone else’s job.

i
Why layers help

When the standard changes, you edit the protocol layer. When your app changes, you edit the data layer. When your rules change, you edit the business layer. Each change stays small.

14Designing the Data Model and Storage

Your server needs a reliable memory. How it stores people decides how fast and how safe it is.

Reuse your application’s accounts

Often the best design is for the SCIM server to read and write your application’s own user table, not to keep a second copy. A second copy can drift out of date and lead to confusing disagreements. If you must keep extra data, such as the identifier Okta knows, store it next to the account in the same place.

Three things worth storing

  • A stable external identifier that you hand to Okta. It never changes and is never reused, even after deletion.
  • A normalized user name used for fast, case-insensitive searches, kept in a column or field with a unique rule.
  • Timestamps and a version tag for created and last-modified, so callers can tell when a record changed.

Uniqueness belongs in the database

Do not rely only on your code to prevent duplicates. Two requests arriving at the same moment can both pass a “does it exist” check and then both create the person. A unique rule enforced by the database blocks the second one. Your code should catch that failure and answer with a conflict reply.

Soft states over deletion

Prefer a simple “enabled or disabled” state over removing rows. Disabled accounts keep their history. Deletion destroys evidence and can orphan data owned by that person. If privacy laws require erasure, handle it as a deliberate, separate process, not as a side effect of deprovisioning.

Groups and membership

If you support groups, store the group, the members, and the link between them. A link table that holds one row per member per group is simple and works well. Make sure removing a user also tidies the links, so no ghost memberships remain.

flowchart LR
    USR["User account
internal id, status"] --- XID["External identifier
given to Okta"] USR --- UNM["Normalized user name
unique"] USR --- TS["Created, modified, version"] USR --- LNK["Membership links"] LNK --- GRP["Group or role"]
Figure 10 · A simple storage layout for users, groups, and their links
Everyday analogy

Think of a school register. Each child has an admission number that never changes, even if they move class or change their surname. The register never throws away a name when a child leaves. It marks them as left. The same careful habits protect your accounts.

!
Race conditions

Two identical create requests can arrive almost together. Only a database-level unique rule reliably stops the second one.

15Mapping Okta Attributes to Your Application

Okta and your application describe people differently. Mapping is the agreement that connects the two descriptions.

Two directions

In Okta, each application has a profile, a list of attributes the app can receive. Administrators decide which Okta fact feeds each app attribute. For example, the app’s user name can come from the Okta email address, and the app’s given name from the Okta first name. This is the Okta-to-app direction. If you also import, there is an app-to-Okta direction.

Choose a small set first

It is tempting to map everything. Resist. Every extra attribute is another thing to validate, secure, and keep in step. Start with the minimum your app truly needs: user name, name, email, and active. Add others only when a real feature needs them.

Declare your app’s attributes

When you set up the integration in Okta, you can define the attributes your app accepts, with their types. Align this with the layouts your server describes. If your server announces an attribute that Okta does not know, administrators cannot map it. If Okta sends an attribute your server does not understand, your server must decide whether to ignore it politely or reject the request. Ignoring unknown optional facts, while returning only what you stored, is usually friendlier.

Handle differences gracefully

  • Lengths: your app may allow fewer characters than Okta does. Validate and answer with a clear error.
  • Formats: dates, phone numbers, and country names come in many styles. Agree on one.
  • Multi-valued facts: Okta may send several emails or phone numbers. Decide which one your app uses, and store the primary.
  • Missing values: decide whether an empty attribute means “leave as is” or “clear it”.
Your app needsTypically fed fromWatch out for
Sign-in nameOkta email or loginCase and trailing spaces
First and last nameOkta first and last nameAccents, long or empty names
EmailOkta primary emailSeveral emails, type labels
DepartmentOkta departmentSpelling differences, length limit
ManagerOkta managerManager must exist before the report
RoleGroup membershipRole names must match exactly
flowchart LR
    UD["Okta profile
first name, email, department"] --> MAP["Mapping rules
set by administrator"] MAP --> OUT["Outgoing SCIM user"] OUT --> SRV["Your server"] SRV --> VAL["Validate lengths and formats"] VAL --> STO["Store in your app"]
Figure 11 · From Okta profile to stored account
Everyday analogy

Think of filling in a visa form using details from your passport. The passport says “Surname,” the form says “Family name.” You copy carefully from one to the other. Mapping is that careful copying, written down once so it happens the same way every time.

i
Keep a mapping sheet

Maintain a simple table of every attribute: where it comes from, where it goes, its limits, and who owns it. It will answer most support questions.

16Retries, Duplicates, and Concurrency

Networks drop messages and callers repeat themselves. A strong server stays correct anyway.

Why repeats happen

Suppose your server creates an account but the reply gets lost on the way back. From Okta’s view, nothing happened, so it tries again later. If your server answers the second create by making another account, you have a duplicate. If it answers with a clear conflict, or returns the existing account, Okta can recover. Design every operation so that repeating it is harmless.

Idempotency in practice

  • Create: check by user name first. If it exists, reply with a conflict so Okta can link.
  • Update: applying the same change twice should leave the same state.
  • Deactivate: switching off an already-off account is a success, not an error.
  • Group membership: adding an existing member or removing an absent member is a quiet success.

Concurrency

Many requests can arrive at once, for different people and sometimes for the same person. Two updates to the same person can overlap and one can overwrite the other. Use database transactions so that each request sees a consistent view. Where possible, use version tags so that an update based on old data is detected.

Ordering problems

Requests can arrive out of order. A group update that mentions a user might arrive before the user is created. Decide how to handle this: either answer with a clear “not found” so Okta retries later, or hold the work briefly. Do not silently drop it.

Rate limits and back-pressure

If thousands of people are assigned at once, Okta may send many requests in a short time. Your application may not handle that. Set a sensible limit and answer with the “too many requests” status, including how long to wait. Okta backs off and tries again. This protects your database from being swamped and is much better than crashing.

sequenceDiagram
    participant O as Okta
    participant S as Your SCIM server
    O->>S: Create user A
    S-->>O: Created, but the reply is lost
    Note over O: No reply seen, so retry later
    O->>S: Create user A again
    S-->>O: Conflict, user already exists
    O->>S: Look up user A
    S-->>O: Here is user A with its identifier
    
Figure 12 · Recovering safely when a reply is lost
Everyday analogy

Think of pressing the lift button twice. A good lift does not call two lifts. The second press changes nothing. Your server should treat repeated requests the same polite way.

!
Silent failure is the enemy

An error that is clear is a gift. A request that appears to succeed but did nothing is a trap that surfaces weeks later in an audit.

17Matching, Importing, and Reconciliation

Most real apps already have users before Okta arrives. Matching them correctly avoids duplicates and surprises.

The matching question

Suppose your app has a person named Asha with a certain email address. Okta also has an Asha with the same email. Are they the same? If you match them, Okta takes control of the existing account. If you do not, Okta may create a second Asha, and one person ends up with two accounts. Matching needs a reliable unique fact, usually the user name or email, chosen on purpose.

Importing existing accounts

Okta can read your list of users through your server and compare it with its own. For this to work, your listing must be complete, stable in order, and fast enough to page through. Before turning on import, clean the data: remove test accounts, fix obviously wrong emails, and merge duplicates. Importing messy data only spreads the mess.

Reconciling differences

Over time, accounts in your app may drift away from Okta, through manual edits or failed updates. A reconciliation check compares both sides and lists differences: people in Okta but missing in the app, people in the app but unknown to Okta, and people whose facts differ. Run it regularly and decide how to fix each type of difference. Do not auto-fix everything. Disabled accounts that should be enabled, or the reverse, deserve a human look.

Orphaned accounts

An orphan is an app account with no matching person in Okta. Orphans often belong to former employees whose offboarding was missed. They are a security risk. A reconciliation report that names orphans is one of the most valuable outputs of a SCIM project.

flowchart TD
    A["Okta people list"] --> C{"Compare by user name"}
    B["App accounts list"] --> C
    C -->|"In both and equal"| OK["Fine"]
    C -->|"In both but different"| DF["Update the differing facts"]
    C -->|"Only in Okta"| CR["Create in the app"]
    C -->|"Only in the app"| OR["Orphan: review before action"]
    
Figure 13 · A reconciliation view of two lists
Everyday analogy

Think of merging two address books. You match people by phone number, join the entries that describe the same person, add the missing ones, and flag the entries that you cannot explain. Rushing leaves you with duplicates and wrong numbers.

i
Run a dry run

Before the first real sync, compare lists and review the proposed changes. A surprise during the first live sync is far more costly than an hour of checking beforehand.

18Connecting Okta to Your Server

Once your server speaks SCIM, you tell Okta where it is and how to use it.

Create the application integration

In the Okta Admin Console you create an application integration for your app. Depending on your release, you may do this by creating a custom integration, or by using an integration wizard, or by publishing to the Okta Integration Network for others to use. The right path depends on whether the app is for your organization alone or for many customers. Check the current documentation for the steps available to you.

Enter the connection details

In the provisioning settings you give the base address of your SCIM server, choose how Okta should sign in to it, and supply the secret. Many consoles offer a test button that makes a simple call and reports success or failure. Use it first, and read any message carefully.

Choose which actions Okta may perform

Okta usually lets you switch on creating users, updating attributes, deactivating users, and, where available, pushing groups and importing users. Turn on only what your server supports and you have tested. Start with create and deactivate, then add the rest.

Map attributes

Choose which Okta facts feed each app attribute, using the mapping sheet from earlier. Include the user name rule. Think about whether the user name should be built from the email, and check how the mapping handles unusual characters.

Assign a test person

Create a test user in Okta, assign them to the integration, and watch your server’s logs. Confirm the lookup, the create, and a follow-up update. Then unassign the person and confirm the account is switched off. Only after these tests pass should real people be assigned.

Roll out in waves

Assign a small pilot group first, such as the project team. Look for errors over a few days. Then widen gradually. A staged rollout means a mistake affects ten people, not ten thousand.

1

Build and test the server alone

Prove it answers correctly before involving Okta.

2

Create the integration

Add the application integration in the Admin Console.

3

Enter address and secret

Press the test button and read the result.

4

Map attributes

Connect Okta facts to your app attributes.

5

Test with one person

Create, update, deactivate, and reactivate.

6

Pilot, then widen

Roll out to a small group, then more.

flowchart LR
    S1["Server tested alone"] --> S2["Integration created"]
    S2 --> S3["Connection test passes"]
    S3 --> S4["Mappings set"]
    S4 --> S5["One test person works"]
    S5 --> S6["Pilot group"]
    S6 --> S7["Everyone"]
    
Figure 14 · The path from first call to full rollout
Everyday analogy

Think of opening a new restaurant. You test the kitchen before inviting guests, run a soft opening for friends, and only then open to the public. Each stage reveals problems when they are still small.

i
Check the documentation

Okta’s screens and wording change over time. Use this chapter as a checklist of ideas and follow the current Okta documentation for the exact clicks.

19Testing Your SCIM Server

A server that seems to work is not the same as a server that has been tested. Testing finds the surprises before your users do.

Test the server on its own first

Before Okta is involved, you can talk to your server with a general-purpose web testing tool. You send a request, read the reply, and compare it with what the standard and Okta expect. This isolates problems: if something fails here, the cause is in your server, not in Okta’s configuration.

The scenarios to cover

  • Search for a user that does not exist and confirm an empty, successful reply.
  • Search for a user that exists, with different capitalization, and confirm it is found.
  • Create a user with only required facts, then with all facts.
  • Create the same user twice and confirm a clear conflict.
  • Read, replace, and patch a user, and confirm the stored result.
  • Deactivate twice and confirm both succeed.
  • Reactivate and confirm the same account returns.
  • Page through a long list and confirm nothing is skipped or repeated.
  • Send bad data and confirm a helpful error.
  • Send no credentials and wrong credentials and confirm rejection.

Use the vendor’s checks

Okta provides guidance and sometimes test suites for SCIM integrations, particularly for those intended for its public catalog. If your server is for internal use only, you can still follow the same checks. Look in the current documentation for the recommended test steps.

Test with the real thing

After standalone tests, connect a non-production Okta organization to a non-production copy of your app. Assign test people with awkward data: long names, accents, missing fields, and many emails. Real data is always messier than imagined.

Automate

Turn your checks into an automatic test collection that runs every time you change the server. Regression is the quiet killer of integrations. A change that fixes one thing often breaks another, and automated tests catch it at once.

Test levelPurposeWho runs it
Unit testsCheck small pieces such as filter parsingDevelopers, on every change
Protocol testsCheck replies against the standardDevelopers, automatically
Integration testsCheck server with the real app databaseDevelopers and testers
End-to-end testsCheck Okta to server to appIdentity team, in a test organization
Load testsCheck behavior with many requestsOperations, before big rollouts
flowchart TD
    UT["Unit tests"] --> PT["Protocol tests"]
    PT --> IT["Integration tests"]
    IT --> E2E["End-to-end with test Okta org"]
    E2E --> LT["Load tests"]
    LT --> PILOT["Pilot group in production"]
    
Figure 15 · A ladder of tests from small to real
Everyday analogy

Think of a new bridge. Engineers test each bolt, then each beam, then the whole span with trucks driving over it, before the public is allowed to cross. Testing a SCIM server works the same way, from small parts to the full load.

i
Keep awkward test people

Maintain a standing set of test users with unusual names, missing fields, and several emails. Reuse them every time you change anything.

20Hosting, Networking, and Reaching Private Apps

Your server must be reachable by Okta, and yet safe from everyone else.

Where it runs

A SCIM server is a small web service. It can run in a cloud platform, in a container, as a serverless function, or on a machine in your own data center. The best home is wherever your application’s data is easiest and safest to reach. A server that runs next to the database is faster and exposes fewer connections.

The public address

Okta runs on the public internet, so a standard integration needs a secure public web address for your server. Use an address you control, with a certificate from a trusted authority. Place a gateway or load balancer in front of the server to handle encryption, limits, and request logging.

Limiting who can call

Beyond credentials, consider restricting by network. Okta publishes lists of the addresses its services use, and these can change over time. If you allow only those addresses, strangers cannot even knock on the door. Treat this as an extra layer, not a replacement for authentication, and plan how you will keep the list current.

When the app is private

Many internal apps live behind a firewall with no public address. Okta offers an on-premises provisioning agent for some cases. This is a small program installed inside your network that opens an outbound connection to Okta and passes requests to a SCIM server that stays private. Availability and details depend on your license and release, so check the documentation before you rely on it.

High availability

If your server is down, no one is provisioned. Okta retries for some time, but long outages create backlogs and errors. Run more than one copy behind a load balancer, keep them stateless, and place them in separate failure zones so a single failure does not stop provisioning.

flowchart LR
    OK["Okta"] -->|"HTTPS"| GW["Gateway or load balancer
certificate, limits, logs"] GW --> S1["SCIM server copy 1"] GW --> S2["SCIM server copy 2"] S1 --> DB["Application database"] S2 --> DB OK2["Okta"] -->|"Outbound only"| AG["On-premises agent"] AG --> PS["Private SCIM server"] PS --> PDB["Private application"]
Figure 16 · Public gateway with two copies, and a private option using an agent
Everyday analogy

Think of a school with a front office. Parcels are delivered to a staffed front desk that signs for them and sends them on, instead of letting couriers wander the corridors. The gateway is the front desk, and it also lets you run two assistants so that the desk never closes.

!
Certificates expire

An expired certificate quietly stops all provisioning. Automate renewal and set an alert that warns weeks before the date.

21Security Considerations

A SCIM server can create and disable accounts, so attackers value it. Defend it in layers.

Authentication and secrets

Every request must be authenticated. Keep secrets in a vault, never in code or in a shared document. Rotate them. Give the secret no more power than needed: the SCIM service account in your app should be able to manage users and nothing else.

Authorization inside your server

Even a properly authenticated caller should be limited. Decide what the SCIM service may and may not do. Should it be able to create administrator accounts? Perhaps role assignment for highly privileged roles should require a separate approval path, so that a stolen secret cannot mint a super-admin.

Validate everything

Treat every incoming value as untrusted. Check lengths, formats, and allowed characters. Reject unexpected attributes or ignore them deliberately. Protect against injection, where cleverly crafted text tries to trick your database or your filter parser into doing something unintended. Use safe database methods that separate data from commands, and parse filters strictly.

Protect personal data

User records are personal data. Send them only over encrypted connections, store them with suitable protection, and limit the facts you accept to what the app needs. Do not log whole records. A log file full of names and emails is a data leak waiting to happen.

Limit abuse

Apply rate limits and size limits. A caller should not be able to send gigantic payloads or request enormous pages. Limit the number of group members accepted in one call.

Protect the off-switch

Make sure that deactivation really disables sign-in, including active sessions and long-lived tokens inside your app. An account marked inactive that can still use an old session is not truly off.

Audit and review

Record who did what and when for every change. Review who has access to the secret and to the server, and review the list of privileged roles that SCIM can grant.

ThreatExampleDefence
Stolen secretAttacker creates an admin accountVault, rotation, limited rights, alerts
InjectionCrafted filter text reaches the databaseStrict parsing and safe queries
Data leakageFull records in logsLog identifiers, not personal facts
AbuseHuge pages or repeated callsSize and rate limits
Incomplete offboardingDisabled account keeps a live sessionEnd sessions and tokens on deactivate
Expired certificateProvisioning stops quietlyAutomated renewal and alerts
flowchart TD
    A["Incoming request"] --> B["Encrypted connection"]
    B --> C["Authenticate caller"]
    C --> D["Rate and size limits"]
    D --> E["Validate every value"]
    E --> F["Authorize the action"]
    F --> G["Safe database access"]
    G --> H["Audit record written"]
    
Figure 17 · Defence in layers, in the order a request meets them
Everyday analogy

Think of a bank vault with a guarded gate, a locked door, a keypad, and a camera. No single defence is perfect, but a thief must beat them all. Layered security makes an attack costly and noisy.

!
Least privilege

Give the SCIM service the smallest rights that make it work. If it is ever misused, the damage is limited.

22Monitoring, Logging, and Troubleshooting

When provisioning fails, you want to know before the help desk does, and you want to find the cause fast.

What to log on your server

Log each request with a time, a unique request number, the verb, the address, the caller, the outcome, and how long it took. Log errors with the reason. Avoid personal data in logs. Include a request number in every reply header so that you can match Okta’s records with yours.

What to watch in Okta

Okta keeps a system log of events, including provisioning successes and failures. Administrators can also see tasks that need attention, such as users that failed to provision, with an error message. Read these regularly. The error text often points straight at the attribute or the status code that caused trouble.

Metrics that matter

  • Request count by operation, to see volume and patterns.
  • Error rate by status, to see when failures begin.
  • Response time, especially the slowest cases.
  • Queue or backlog size, if you process some work later.
  • Certificate and secret expiry dates.
  • Reconciliation differences, counted over time.

Alerts

Alert on sudden changes: a jump in errors, a drop to zero requests when you expect traffic, slow replies, and approaching expiry dates. Alert on any deactivate failures separately, because they are the dangerous ones.

A troubleshooting routine

Start from the person. Find them in Okta, read the provisioning error, and note the time. Find the matching request in your server logs. Read the status and the detail. Reproduce with the testing tool using the same data. Decide whether the cause is the data, the mapping, the credentials, the network, or a bug. Fix it, then retry the failed task in Okta.

flowchart TD
    P["Person failed to provision"] --> O["Read the Okta error and time"]
    O --> L["Find the request in server logs"]
    L --> R["Read status and detail"]
    R --> T["Reproduce with a test request"]
    T --> C{"Cause?"}
    C -->|"Data"| D1["Fix source data or validation"]
    C -->|"Mapping"| D2["Fix the attribute mapping"]
    C -->|"Credentials or network"| D3["Fix secret, certificate, or address"]
    C -->|"Bug"| D4["Fix the code and add a test"]
    D1 --> RT["Retry the task"]
    D2 --> RT
    D3 --> RT
    D4 --> RT
    
Figure 18 · From a failed task to a fixed account
Everyday analogy

Think of a doctor following a patient’s story: what happened, when, what the tests say. The doctor does not guess. Logs and metrics are the tests that let you diagnose by evidence.

i
Make errors readable

The best troubleshooting tool is an error message that names the attribute and the problem. Spend time making yours precise.

23Performance and Scalability

Provisioning is usually quiet, but it can arrive in bursts. A well-built server stays calm during a burst.

Where bursts come from

On most days only a trickle of changes arrives. But when a new application is rolled out to thousands of people, or a large reorganization changes departments for everyone, requests can pile up in minutes. A server designed only for the quiet days may fall over on the busy one.

Make the common calls cheap

The lookup by user name is called constantly. Put an index on the normalized user name so it is a quick lookup rather than a scan of the whole table. Return only the facts needed. Avoid heavy work, such as sending emails or calling slow systems, inside the request itself.

Do slow work later

If creating an account triggers slow steps, such as setting up mailboxes or sending welcome notes, queue those steps and complete the SCIM request quickly. Okta can wait only so long for a reply. Be careful, though: if a user is reported as created before the slow work is done, make sure your app handles the half-ready state gracefully.

Scale out, not just up

A stateless server can run as several identical copies behind a load balancer. Add copies when load rises. Make sure your database can cope too: it is often the real limit. Watch connection counts and slow queries.

Respect the limits on both sides

Okta applies its own rate limits to its services. Your server should apply sensible limits too. When you cannot keep up, reply with the “too many requests” status and a wait time, and Okta will slow down. This is a far better outcome than timeouts and half-finished work.

Plan with real numbers

Estimate your largest burst: how many people might be assigned at once, and how many requests each one needs (usually a lookup, a create, and perhaps a few updates). Multiply, test with that load, and add a safety margin.

Index
the user name for fast lookups
Queue
slow side tasks for later
Scale
stateless copies behind a balancer
flowchart LR
    BURST["Burst of requests"] --> LB["Load balancer"]
    LB --> A["Server copy A"]
    LB --> B["Server copy B"]
    LB --> C["Server copy C"]
    A --> DB["Indexed database"]
    B --> DB
    C --> DB
    A -.-> Q["Queue for slow tasks"]
    B -.-> Q
    C -.-> Q
    
Figure 19 · Spreading a burst across copies and deferring slow work
Everyday analogy

Think of a supermarket at festival time. More checkout lanes open, shelves are stocked in advance, and the bakery bakes cakes in the back instead of at the till. Quick lanes plus background work keep queues short.

24Advantages, Disadvantages, and Trade-offs

A custom server is powerful, but it is also a piece of software you must own for years.

Advantages

  • Joiners get access automatically and quickly.
  • Leavers lose access promptly, reducing risk.
  • Fewer manual tasks and fewer typing errors.
  • Standard language that other tools can reuse.
  • Clear audit trail of who was created, changed, and removed.
  • Works for apps that have no catalog connector.

Disadvantages

  • You must build, host, secure, and maintain a service.
  • Needs skills in the protocol and in your own app.
  • Errors can silently leave wrong access.
  • Standards and Okta behavior evolve over time.
  • Messy source data causes visible failures.
  • The service becomes a sensitive target for attackers.

Choosing between options

A custom server is only one way to reach an application. The right choice depends on size, risk, and the skills you have.

OptionBest whenCostRisk
Catalog connectorThe app is a known productLowLow
Custom SCIM serverYour app has users and changes oftenMedium to highMedium, depends on quality
On-premises agent with private serverThe app is private behind a firewallMedium to highMedium
Scheduled file importThe app can only read filesLow to mediumSlower, less precise
Manual updatesVery few users and rare changesLow at firstHigh as it grows
Single sign-on only, accounts created on first useProvisioning is not requiredLowLeavers keep stale accounts
flowchart TD
    N["App needs user lifecycle automation"] --> Q1{"Catalog connector exists?"}
    Q1 -->|"Yes"| U1["Use the connector"]
    Q1 -->|"No"| Q2{"App can expose a SCIM front door?"}
    Q2 -->|"Yes, reachable"| U2["Build a custom SCIM server"]
    Q2 -->|"Yes, but private"| U3["Server plus on-premises agent"]
    Q2 -->|"No"| Q3{"Few users and rare change?"}
    Q3 -->|"Yes"| U4["Manual or file-based process"]
    Q3 -->|"No"| U5["Consider changing or wrapping the app"]
    
Figure 20 · A decision path for choosing how to provision an app
Everyday analogy

Think of getting to work. A direct bus is cheapest when it exists. Building your own road is a large job that makes sense only when many people will use it for many years. The custom server is the road, and you should build it with open eyes.

25Design Patterns for SCIM Servers

A pattern is a proven answer to a problem that keeps coming back. These patterns make a SCIM server calmer and safer.

Pattern: Look first, then create

Before creating a person, search by user name. If the person exists, reply with a conflict or return them. This one habit prevents most duplicates, and it matches how Okta itself behaves.

Pattern: Soft off-switch

Deactivate by flipping an enabled flag and ending sessions, not by deleting rows. History stays intact and reactivation is trivial.

Pattern: Stable external identifier

Hand Okta an identifier that never changes and is never reused. Keep it separate from the user name and the email address, which can both change.

Pattern: Thin protocol, thick rules

Keep the SCIM-specific code thin. It parses and replies, nothing more. Put your decisions in a separate business layer. When the standard evolves, the rules stay untouched.

Pattern: Attribute ownership table

Write down which attributes Okta owns and which your app owns. The server updates only the first group. This protects local settings from being erased by a replace request.

Pattern: Idempotent by design

Make every operation safe to repeat. Duplicate deactivate, duplicate add-member, and duplicate create all end in the same correct state.

Pattern: Fail loudly, with detail

When you cannot do something, say so with the right status and a message that names the attribute and the reason. A loud failure is fixed in minutes. A silent one lives for months.

Pattern: Reconcile on a schedule

Do not assume both sides stay in agreement forever. Compare them regularly, report differences, and fix on purpose.

Safety

Look first, then create

Search before making anyone.

History

Soft off-switch

Disable, do not delete.

Identity

Stable identifier

Never reuse, never change.

Structure

Thin protocol layer

Rules live elsewhere.

Ownership

Attribute table

Okta fields versus app fields.

Trust

Reconcile regularly

Find drift before auditors do.

flowchart TD
    REQ["Create request"] --> LK{"User name exists?"}
    LK -->|"Yes"| CF["Reply conflict or return existing"]
    LK -->|"No"| VAL["Validate and apply ownership rules"]
    VAL --> DB["Insert with unique rule"]
    DB --> OK{"Insert succeeded?"}
    OK -->|"Yes"| CR["Reply created with identifier"]
    OK -->|"Duplicate raced"| CF
    
Figure 21 · Several patterns working together in a create request
Everyday analogy

Think of a careful librarian. Before issuing a new card, she checks the register. She never throws away old records. She gives each reader a number that never changes. She keeps her own notes separate from the registry. Patterns are simply the good habits of a careful professional.

26Anti-patterns to Avoid

An anti-pattern is a habit that seems sensible but causes trouble later. Each card below shows one, why it hurts, and a better way.

AP-01 · THE OPEN DOOR Avoid
What it looks like

A SCIM address with weak or no authentication, left open because “it is only internal”.

Why it hurts

Anyone who finds it can create or disable accounts, including administrators.

Better approach

Require strong credentials on every call, restrict the network, and keep secrets in a vault.

AP-02 · THE HARD DELETE Avoid
What it looks like

Removing the account row whenever a person is deprovisioned.

Why it hurts

History vanishes, owned data is orphaned, and returning staff get a new, empty account.

Better approach

Disable the account and end sessions. Delete only through a deliberate, separate process.

AP-03 · THE EMAIL AS IDENTIFIER Avoid
What it looks like

Using the email address as the permanent identifier handed to Okta.

Why it hurts

When the email changes after a name change, the link breaks and duplicates appear.

Better approach

Use a separate, stable identifier that never changes.

AP-04 · THE SILENT SUCCESS Avoid
What it looks like

Replying “success” even when an attribute was ignored or the work failed.

Why it hurts

Okta believes the app is correct, while the app has wrong or missing data.

Better approach

Return the stored record, and use clear errors when something cannot be done.

AP-05 · THE ZERO-BASED PAGER Avoid
What it looks like

Treating the first position in a list as zero instead of one.

Why it hurts

One person is skipped at every page boundary, and the loss is hard to notice.

Better approach

Follow the standard’s one-based positions and test with lists longer than a page.

AP-06 · THE EVERYTHING MAPPING Avoid
What it looks like

Mapping every possible Okta attribute into the app “just in case”.

Why it hurts

More data to validate, secure, and keep correct, and more ways for requests to fail.

Better approach

Map only what a real feature needs, and add more later with a reason.

AP-07 · THE LOGGED PERSON Avoid
What it looks like

Writing complete user records into log files for debugging convenience.

Why it hurts

Logs spread widely and become an uncontrolled copy of personal data.

Better approach

Log request numbers and identifiers, and keep personal facts out.

A provisioning server is a promise: whoever Okta says should have access does, and whoever should not, does not.

27Best Practices and Common Mistakes

This chapter gathers habits that separate calm teams from frantic ones.

Best practices

1

Start small

Support users, search, create, update, and deactivate before anything else.

2

Follow the standard

Use the documented layouts, statuses, and one-based paging.

3

Secure from day one

Encrypted traffic, strong credentials, vault, rotation, least privilege.

4

Make it repeatable

Every operation safe to run twice.

5

Test automatically

A growing test collection, run on every change.

6

Observe

Logs, metrics, and alerts, including expiry dates.

7

Roll out in waves

Pilot group first, then widen gradually.

8

Reconcile and review

Compare both sides regularly and review access to the service itself.

Common mistakes

MistakeWhat happensFix
Case-sensitive user name searchDuplicates with different capital lettersNormalize and compare without case
Empty search treated as errorOkta cannot create new peopleReturn success with an empty list
Only supporting replaceGroup and status changes failSupport both replace and patch
Not handling repeated requestsDuplicates after retriesMake operations idempotent
Ignoring rate limitsDatabase overloaded in a rolloutLimit and answer with a wait time
Forgetting session cleanupDeactivated users stay signed inEnd sessions on deactivate
Testing only perfect dataReal users fail on awkward namesKeep awkward test people
No ownership of the serviceNobody fixes it when it breaksName an owner and an on-call route
Letting certificates expireAll provisioning stops quietlyAutomate renewal, alert early
Everyday analogy

Think of a pilot’s checklist. Experienced pilots still use one, not because they forget how to fly, but because small slips matter. These practices are the checklist for your server.

i
One-minute review

Before going live, ask three things: what happens when this is repeated, what happens when it fails, and who will notice.

28Real-World Scenarios

These short stories use made-up companies to show how the ideas appear in daily work. They are teaching examples, not descriptions of any real organization.

Scenario 1: The claims portal nobody could automate

Marlowe Insurance ran an internal claims portal built years earlier. New adjusters waited days for accounts, and leavers sometimes kept access for weeks. The team put a thin SCIM layer in front of the portal’s user table, supporting search, create, update, and deactivate. They piloted with one department, fixed two mapping issues with long surnames, and widened over a month. Account setup dropped from days to minutes, and an audit later showed no active accounts for people who had left.

Scenario 2: Duplicate Ashas

Kestrel Logistics enabled provisioning to a partner extranet that already had accounts. Okta created a second account for many people because the extranet compared user names with case sensitivity, and many existing names had capitals. The team changed the search to ignore case, merged the duplicates, and ran a dry-run comparison before re-enabling. A reconciliation report now runs weekly.

Scenario 3: The Monday burst

Northgate Retail assigned a new scheduling app to eight thousand store staff on one Monday morning. The SCIM server answered quickly at first, then the database saturated. The team added an index on the normalized user name, enabled a rate limit that returned a wait time, and queued welcome emails for later. Okta slowed its pace and finished assigning in under two hours with no failures. The change was added to their rollout checklist.

Scenario 4: The expired certificate

Cedarline Health found on a Friday that no new nurses had been provisioned for six days. The cause was an expired certificate on the SCIM gateway. The team automated renewal, added an alert thirty days before expiry, and added a monitor that warns when requests drop to zero during working hours.

What the scenarios share

Each story was solved by the same habits: look first before creating, handle repeats, make failures visible, test with real data, roll out in stages, and watch for quiet breakage. None needed anything exotic. They needed discipline.

i
Lesson

Most provisioning incidents come from data surprises and silent failures, not from clever attacks. Boring care beats clever tricks.

29Frequently Asked Questions

Short answers to common questions. Where details depend on your license or release, confirm them in your own console and the official documentation.

Q1What does a custom SCIM server do?
It receives standard requests from Okta, such as create, update, and deactivate, and carries them out inside your application. It also answers questions like “does this user already exist” and “list your users”.
Q2Does Okta host the server for me?
No. You build and host the service, or a vendor hosts it for you. Okta is the client that calls it. For private apps, an on-premises agent may help Okta reach a server inside your network, depending on your license and release.
Q3Should I use SCIM version 1.1 or 2.0?
Version 2.0 is the modern choice and is widely supported. Check what your Okta integration type supports and choose accordingly.
Q4What is the smallest useful server?
One that authenticates callers, searches users by user name, creates users, updates users, and deactivates users. Add groups, import, and the descriptive endpoints afterwards.
Q5Why does Okta search before creating?
To avoid duplicates. If a person already exists in your app, Okta can link to the existing account instead of making another one.
Q6What should an empty search return?
A successful reply with a total of zero and an empty list. It is not an error. Okta reads it as “no such person”.
Q7How should I deactivate a user?
Set the active flag to false, disable sign-in, and end active sessions and tokens. Keep the record. Make the operation safe to repeat.
Q8Do I have to support groups?
Only if you want Okta to manage group or role membership in your app. Many integrations work well with users only.
Q9Replace or patch: which one will Okta use?
It can use either, depending on the task and integration settings. Support both on the users desk so you are covered.
Q10How should my server authenticate Okta?
Use one of the methods your Okta integration supports, such as a secret token or OAuth 2.0, over encrypted traffic. Prefer short-lived tokens where possible, store secrets in a vault, and rotate them.
Q11What should the identifier be?
A unique, stable value created by your server that never changes and is never reused. Do not use the email address or the user name.
Q12What if two requests create the same person at once?
Rely on a unique rule in the database. One request wins and the other fails, and your server should reply with a conflict.
Q13How do I handle attributes my app does not store?
Decide per attribute whether to store, ignore, or reject. For optional facts, ignoring while returning only what you stored is usually friendly. Document the choice.
Q14How can I test without affecting real users?
Use a general testing tool against a non-production copy of your app, then a test Okta organization with test people, then a small pilot group in production.
Q15Why did provisioning suddenly stop?
Common causes are an expired certificate, a changed or expired secret, a changed address, a network rule, or the server being down. Check Okta’s error messages and your server’s logs.
Q16How do I import existing accounts safely?
Clean the data first, make sure your listing pages correctly in stable order, choose a reliable matching attribute, and review a dry-run comparison before the first real sync.
Q17Can I use the same server with other identity tools?
Often yes, because SCIM is a public standard. Differences in behavior may exist, so test each tool separately.
Q18Will this guide match my console exactly?
Possibly not. Okta changes names, features, and defaults over time, and what you can use depends on your license. Use the guide as a map of the ideas and confirm details in the official documentation and the SCIM standards.

30Summary and Key Takeaways

Here is the whole story in a few lines you can carry with you.

A custom SCIM server is a translator that sits in front of your application. Okta speaks the standard language of identity: look up this person, create them, change their facts, switch them off. Your server turns each request into the right action inside your app and replies honestly with the correct status. Built in clear layers, protected by strong authentication, tested with awkward data, watched with logs and alerts, and rolled out in stages, it keeps accounts in step with people automatically.

The craft lies in small habits: search before creating, never delete when you can disable, use stable identifiers, make every operation safe to repeat, and fail loudly with clear detail. With these, the service becomes a quiet, dependable part of your access design instead of a hidden source of surprises.

Key Takeaways

  • Okta asks, your server answers. Your job is to translate standard SCIM requests into actions in your app and report truthfully.
  • Start with the essentials. Authenticate, search by user name, create, update, and deactivate. Add groups and import later.
  • Be safe to repeat. Look before creating, treat repeated deactivation as success, and use database rules to stop duplicates.
  • Use stable identifiers and soft deactivation. Never use the email as the identifier, and disable rather than delete.
  • Secure the door. Encrypted traffic, strong credentials in a vault, least privilege, strict validation, and no personal data in logs.
  • Test, observe, and roll out in waves. Automate tests, watch logs, metrics, and expiry dates, and widen from a pilot group.
  • Confirm the details. Okta behavior and options change, so verify specifics in the official documentation and the SCIM standards.