External Identifiers
This guide explains how to configure and use the external_id
field in Ory Kratos to support external primary identifiers such as
customer_id
, employee_id
, or similar. This is especially useful for migrations from systems where you need to preserve
identifiers or support user-defined primary identifiers.
The external_id
must be unique across all identities. If you attempt to import multiple identities with the same external_id
,
the operation will fail with a 409 Conflict
.
Overview
Traditionally, Ory Kratos identifies users using an internal identity.id
UUID. With the external_id
feature, you can:
- Assign a unique, domain-specific identifier to each identity.
- Query and manage identities using
external_id
. - Use
external_id
as thesub
(subject) claim in JWTs. - Preserve identity semantics across systems during migration.
This helps simplify migrations, reduce mapping layers, and align Kratos with your existing infrastructure.
Configuration
Use external_id
via API, not schema
The external_id
is not part of the identity JSON Schema. Instead, it is a dedicated top-level attribute in API requests that
create or update identities.
Do not add external_id
to your identity schema definition. It is handled separately by Ory Kratos internally.
Use external_id
in JWT sub
claim
Set the subject_source
to external_id
in the tokenization config:
session:
whoami:
tokenizer:
templates:
jwt_template_1:
jwks_url: base64://... # A JSON Web Key Set (required)
claims_mapper_url: base64://... # A JsonNet template for modifying the claims
ttl: 1m # 1 minute (defaults to 10 minutes)
subject_source: external_id
another_jwt_template:
jwks_url: base64://... # A JSON Web Key Set
This will populate the sub
claim in JWTs with the value of external_id
.
If external_id
is not set for a user when subject_source
is external_id
, tokenization will fail.
API usage
Create an identity with external_id
POST /admin/identities
Content-Type: application/json
{
"schema_id": "default",
"traits": {
"email": "user@example.com"
},
"external_id": "customer-12345"
}
Get identity by external_id
GET /admin/identities/by/external/customer-12345
Optional query parameter
include_credential=password,oidc,...
— Include specific credentials in the response.
Example:
GET /admin/identities/by/external/customer-12345?include_credential=password
Response:
{
"id": "uuid-abc123",
"external_id": "customer-12345",
"traits": {
"email": "user@example.com"
},
"credentials": {
"password": { ... }
}
}
Error responses:
404
– Identity not found.409
– Duplicateexternal_id
on creation.400
– Invalid request structure.
There are no other APIs that support external_id
, for the APIs that require a Kratos identity_id
you need to use the
Get identity by external_id API above and use the identity id from there.
JWT configuration
Jsonnet example
When tokenizing sessions, external_id
is available in the session context:
local claims = std.extVar('claims');
local session = std.extVar('session');
{
claims: {
iss: claims.iss + "/additional-component",
schema_id: session.identity.schema_id,
external_id: session.identity.external_id,
session: session,
}
}
Token behavior with external_id
If subject_source
is set to external_id
in the tokenizer template, the JWT's sub
claim becomes:
{
"sub": "customer-12345"
}
If external_id
is missing, tokenization will fail.
Migration guide
To migrate from an existing system, you can bulk import identities into Kratos and set their external_id
using the
Identity Import API.
Use PATCH /admin/identities
Basic import example
[
{
"schema_id": "default",
"external_id": "customer-001",
"traits": {
"email": "alice@example.com"
}
}
]
Pre-hashed password example
[
{
"schema_id": "default",
"external_id": "customer-002",
"traits": {
"email": "bob@example.com"
},
"credentials": {
"password": {
"config": {
"hashed_password": "$2b$12$abc123..." // bcrypt hash
}
}
}
}
]
Migration tips
- Pre-hash passwords to avoid timeouts
- Validate
external_id
uniqueness before import - Import in smaller batches (≤ 200 identities if using plaintext passwords)
Troubleshooting
Error / Code | Context | Description |
---|---|---|
409 Conflict | Migration (batch import) | One or more identities have a duplicate external_id . Ensure all values are unique. |
400 Bad Request | Migration (batch import) | The request payload is invalid or improperly formatted. Check JSON structure and required fields. |
504 Gateway Timeout | Migration (batch import) | The batch is too large or includes plaintext passwords. Reduce batch size or pre-hash passwords. |
500 Internal Server Error | Session token generation | If subject_source = external_id is configured, the session will not tokenize unless the identity has an external_id set. |
For advanced examples, see: