How to Create and Integrate an Okta OIDC Service Account with Neo4j

Photo of Klaus Mueller

Klaus Mueller

Technical Support Engineer at Neo4j

What will we build?

If you are using Okta as your identity provider, the recommended approach for service-to-service authentication is to use an API Services application with OAuth 2.0 Client Credentials.

In this guide, you will learn how to:

  • Create a service application in Okta
  • Configure Neo4j to enable SSO authentication
  • Request an access token using the client credentials flow
  • Authenticate to Neo4j using a JWT via the Python driver

Why use service accounts with Neo4j?

A service account provides your application with a non-human identity.

Instead of storing Neo4j username and password credentials in scripts or environment variables, you can issue short-lived OAuth 2.0 access tokens from Okta and allow Neo4j to validate them.

This approach offers several advantages:

  • Improved secret management (no long-lived credentials)
  • Easier rotation and revocation of access
  • Fine-grained control over scopes and claims
  • Clear auditability for automated workloads

Prerequisites

Before getting started, ensure you have:

  • An Okta organization (for example, your-domain.okta.com)
  • A Neo4j instance (self-managed or AuraDB)
  • The Neo4j Python Driver is installed

Step 1: Create an Okta Service Application

In the Okta Admin Console (https://your-domain.okta.com/admin/home):

  1. Navigate to Applications → Create App Integration
Okta Create App Integration Screen

2. Select:

  • OIDC — OpenID Connect
  • Application type: API Services
Okta App Integration

3. Provide a name for your application (for example: neo4j-service-account)

4. Configure the application claims:

  • Locate the Application Profile Attributes section
  • Add the following custom attribute:{“neo4j_groups”: [“neo4j_admin”]}.
Application Profile Attributes

This attribute will later be used by Neo4j to map roles and control access.

Theneo4j_admin is used here as a simple example and maps to the Neo4j admin role, which has broad privileges.
For production, follow least privilege and use roles like reader or publisher. See built-in roles.

5 . Configure client security settings:

  • Navigate to the General Settings section of your application
  • Disable Proof of Possession (DPoP)
Okta General Settings

This ensures that Okta issues standard bearer tokens, which are required for compatibility with Neo4j. Neo4j does not currently support DPoP-bound tokens, so leaving this enabled would prevent successful authentication.

6. After creation, copy and securely store:

  • Client ID
  • Client Secret

These credentials will be required to request access tokens in the next steps.

Step 2: Configure API Scopes and Claims

In the Okta Admin Console:

  • Navigate to Security → API
  • Select the Default Authorization Server
Default Authorizaiton Server

2. Go to the Scopes tab:

  • Ensure the default openid scope is present
  • Click Add Scope and create a new custom scope:
  • Name: neo4j_groups
Scopes
Add Scope neo4j_groups

3. Go to the Claims tab and click Add Claim

Add Claim

4. Create a claim with the following settings:

  • Name: groups
  • Include in token type: Access Token
  • Value type: Expression
  • Value: app.profile.neo4j_groups
  • Include in: Specific scopes
  • Scope: neo4j_groups
Add Claim

This maps the custom attribute defined in Step 1 to a claim that will be included in the issued JWT.

5. Navigate to the Access Policies tab

Access Policies

6. Create a new policy for your Neo4j service application

Add Neo4j Policy

7. Add a policy rule that:

  • Allows the client to request tokens
  • Includes the scopes: openid and neo4j_groups
Add Scopes for Policy Rule

Step 3: Configure SSO in Neo4j

Now we need to configure Single Sign-On (SSO) in your Neo4j environment to trust tokens issued by Okta.

Deployment considerations

The configuration process depends on how Neo4j is deployed:

  • Self-managed (on-premise / VM):
    You can configure SSO directly in the Neo4j configuration file by following the official documentation: Configuring Neo4j.
  • Neo4j Aura:
    Instance-level SSO configuration is managed by the platform and cannot be modified directly. In this case, refer to the available documentation in Instance-level SSO support.

Expected configuration

At the end of this step, your Neo4j server should be configured to:

  • Trust the Okta issuer (Authorization Server)
  • Validate incoming JWT access tokens
  • Extract identity and group information from token claims
  • Map claims to Neo4j roles

A typical configuration will include:

  • The OIDC discovery endpoint
  • The expected audience
  • The username claim
  • The groups claim

Example configuration

Below is an example of what the SSO configuration may look like in a self-managed Neo4j deployment:

dbms.security.authorization_providers=native,oidc-okta-backend
dbms.security.authentication_providers=native,oidc-okta-backend

dbms.security.oidc.okta-backend.display_name=Okta Backend
dbms.security.oidc.okta-backend.visible=false
dbms.security.oidc.okta-backend.auth_flow=pkce
dbms.security.oidc.okta-backend.well_known_discovery_uri=https://integrator-xxxxxx..okta.com/oauth2/default/.well-known/openid-configuration
dbms.security.oidc.okta-backend.audience=api://default
dbms.security.oidc.okta-backend.claims.username=sub
dbms.security.oidc.okta-backend.params=client_id=<client_id>;response_type=code;scope=openid profile email neo4j_groups api://default
dbms.security.oidc.okta-backend.config=principal=sub;code_challenge_method=S256;token_type_principal=access_token;token_type_authentication=access_token
dbms.security.oidc.okta-backend.claims.groups=groups
dbms.security.oidc.okta-backend.authorization.group_to_role_mapping="neo4j_admin"=admin

Step 4: Connect to Neo4j Using the Python Driver

Now that your Okta service application and Neo4j SSO configuration are in place, you can authenticate using a JWT access token and connect to the database.

The following Python script demonstrates how to:

  • Request an access token from Okta using the Client Credentials flow
  • Authenticate to Neo4j using that token
  • Execute a simple query to validate the connection
import base64
import json
import os

import requests
from neo4j import GraphDatabase, bearer_auth


def require_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"Missing required environment variable: {name}")
return value


def get_okta_token() -> str:
okta_domain = require_env("OKTA_DOMAIN") # e.g. integrator-xxxxxx.okta.com
okta_client_id = require_env("OKTA_CLIENT_ID")
okta_client_secret = require_env("OKTA_CLIENT_SECRET")
okta_scope = os.getenv("OKTA_SCOPE", "neo4j_groups")

token_url = f"https://{okta_domain}/oauth2/default/v1/token"
basic = base64.b64encode(
f"{okta_client_id}:{okta_client_secret}".encode("utf-8")
).decode("utf-8")

response = requests.post(
token_url,
headers={
"Authorization": f"Basic {basic}",
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
},
data={"grant_type": "client_credentials", "scope": okta_scope},
timeout=30,
)

# Show Okta JSON error details for fast troubleshooting.
if response.status_code >= 400:
try:
detail = json.dumps(response.json(), indent=2)
except Exception:
detail = response.text
raise RuntimeError(
f"Okta token request failed ({response.status_code}) at {token_url}\n{detail}"
)

payload = response.json()
token = payload.get("access_token")
if not token:
raise RuntimeError(
f"Token response missing access_token. Keys={list(payload.keys())}"
)
return token


def connect_and_query_neo4j(access_token: str) -> None:
neo4j_uri = require_env("NEO4J_URI") # e.g. neo4j+s://<id>.databases.neo4j.io:7687
neo4j_db = os.getenv("NEO4J_DATABASE", "neo4j")

with GraphDatabase.driver(neo4j_uri, auth=bearer_auth(access_token)) as driver:
driver.verify_connectivity()
with driver.session(database=neo4j_db) as session:
record = session.run("SHOW CURRENT USER").single()
print(record.data())


def main() -> None:
token = get_okta_token()
connect_and_query_neo4j(token)


if __name__ == "__main__":
main()

Expected result

If the authentication is successful, you should see output similar to:

{'user': '<sub>', 'roles': ['admin', 'PUBLIC'], 'passwordChangeRequired': False, 'suspended': False, 'home': None}

This confirms that:

  • The token was successfully issued by Okta
  • Neo4j validated the token
  • Role mapping (via the groups claim) is working as expected

Troubleshooting

In case of any issue during the authentication, review the following Knowledge Articles for troubleshooting steps:


How to Create and Integrate an Okta OIDC Service Account with Neo4j was originally published in Neo4j Developer Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.