Showing posts with label REST API. Show all posts
Showing posts with label REST API. Show all posts

Sunday, December 07, 2025

OAuth Grant Types in ServiceNow: A Practical Guide for External Integrations (Updated)

OAuth Grant Types in ServiceNow — A Practical Guide for External Integrations

Choosing the wrong OAuth grant type is one of the most common causes of failed integrations in ServiceNow. Symptoms include:

  • Session expired messages
  • Invalid CSRF token errors
  • Unexpected redirects to login pages
  • Token endpoint failures
  • API calls succeeding in one instance but failing in another

This article explains the five OAuth grant types ServiceNow supports for inbound integrations, what each one is designed for, and which external tools should use which flow.

Why grant type matters

When an external tool requests an access token, ServiceNow evaluates how the client identifies itself, whether a user's credentials are required, whether a redirect URL is needed, and whether the request is browser-based or server-to-server.

If the wrong grant type is used, ServiceNow may reject the request, redirect to a login page, return HTML instead of JSON, or treat the call as UI traffic and enforce CSRF protection. This is why integrations like AWS Glue, which expect simple JSON token responses, fail when the grant type is incorrect.

The OAuth grant types available in ServiceNow

1. Client Credentials (most common for integrations)

Best for: server-to-server integrations, tools that authenticate using only a client ID and secret, and automated systems with no user interaction.

Examples: AWS Glue, MuleSoft, Boomi, custom API clients, backend services.

How it works: the integration authenticates with a client ID and client secret only. No user login, no redirect, no password.

Why it's recommended: it's simple, secure, stable, has no user dependency or browser interaction, and behaves consistently across environments — which is exactly why ServiceNow documents it as the standard for machine-to-machine access.

Common error when not used: tools expecting this flow may receive "session expired" or CSRF errors when the OAuth registry is configured with a different grant type instead.

2. Resource Owner Password Credentials (ROPC)

Best for: legacy applications that authenticate using a specific user's username and password.

Why it's risky: it requires storing a user's password in an external system, breaks if that password expires or the user is locked, and is disallowed under many organizations' security policies.

A note on ROPC's status: ROPC is deprecated under the OAuth 2.1 specification, and ServiceNow provides a system property, glide.oauth.inbound.ropc.grant_type.disabled, that admins can set to true to block it instance-wide. If that property is enabled and an OAuth app registry is still set to ROPC, every token request against it will fail outright.

Why integrations fail with ROPC: if the integration expects to authenticate using just a client ID and secret (like Glue), but the OAuth registry is set to ROPC, ServiceNow rejects the request and redirects to the login page. The external system receives HTML instead of a token, which surfaces as a misleading CSRF or session-expired error.

Use only if: you absolutely must impersonate a specific user and have no modern integration path — and even then, treat it as a temporary measure rather than a long-term design choice.

3. Authorization Code Grant (with redirect)

Best for: web apps where a human signs in, browser-driven flows, and portals that require explicit user consent.

Examples: custom web apps, portals, internal tools with UI login pages.

Characteristics: requires a redirect URL, requires a human user, has a longer setup, and is not suitable for automated systems. Confidential clients use a client secret; public clients (mobile apps, SPAs) can use PKCE (Proof Key for Code Exchange) instead, which avoids storing a secret on a client that can't keep one safe.

Why integrations fail with this: if an automated tool uses this grant type, ServiceNow tries to redirect it to a login page, which causes "session expired."

4. JWT Bearer Grant

Best for: secure server-to-server access, either on behalf of a user or as the client application itself, without requiring real-time user interaction or a stored shared secret.

How it works: the client creates a signed JSON Web Token containing identity claims and presents it to ServiceNow's token endpoint. ServiceNow validates the signature and claims before issuing an access token — no password or interactive login involved.

When to use it over Client Credentials: when you need the extra assurance of a cryptographically signed request, or need to assert a specific identity (user or system) without exchanging a long-lived shared secret. For most simple system-to-system scenarios, Client Credentials remains simpler to set up and is the more common default.

5. Refresh Token Grant

Best for: extending an existing session without re-authenticating.

Works only after: the Authorization Code or JWT Bearer flow has already created a token. Not used alone — it's always a secondary mechanism layered onto another grant type.

Grant type comparison

Grant Type Use Case Requires User Login Works for Automation Recommended for APIs
Client CredentialsServer-to-serverNoYesYes
Resource Owner PasswordLegacy user-based authYesNoNo (deprecated)
Authorization Code (+ PKCE)Browser/web loginYesNoNot for automation
JWT BearerSigned, secretless server-to-serverNoYesYes (advanced)
Refresh TokenExtend existing tokenSometimesYesConditional

How to check the grant type in ServiceNow

Go to System OAuth > Application Registry > [Your OAuth App] and look for the Grant type field. Allowed values will show one or more of: Client Credentials, Resource Owner Password Credentials, Authorization Code, JWT Bearer, and Refresh Token. If the wrong type is selected, update it and re-test.

Why grant type issues only affect one instance

A common scenario: the integration works in DEV, works in PROD, but fails in TEST. This happens because the client ID is different, the grant type is different, scopes differ, the redirect URL differs, or the integration user is locked only in that one instance. Even a single field mismatch is enough to break OAuth.

Best practices for reliable integrations

  • Use Client Credentials (or JWT Bearer) for non-user-driven systems. Simple, predictable, secure.
  • Avoid Resource Owner Password Credentials — it's deprecated, risky, and breaks easily. Consider setting glide.oauth.inbound.ropc.grant_type.disabled to true once you've confirmed nothing still depends on it.
  • Disable unused grant types on each OAuth application registry to reduce confusion and shrink the attack surface.
  • Standardize OAuth registry configuration across all instances using a controlled update set or automation job, rather than manual per-instance setup.
  • Clearly document client ID and secret per environment to prevent accidental cross-environment use.

Final thoughts

Most OAuth-related integration failures in ServiceNow come down to one root cause: incorrect grant type selection. By understanding how each of the five grant types works and matching it to the correct use case, teams can avoid authentication errors, reduce support overhead, and keep integrations stable across every environment.


This article reflects ServiceNow's current OAuth Inbound documentation, including JWT Bearer and PKCE support for Authorization Code. Available grant types and default behavior can vary by release family and active plugins — verify against your own instance's Application Registry before making changes.

ServiceNow CSRF Token Errors in Integrations: Root Causes and Fixes (Updated)

Understanding ServiceNow CSRF Token Errors in Integrations

CSRF protection in ServiceNow is designed to guard browser-based UI sessions, not machine-to-machine API traffic. So when an external tool such as AWS Glue receives "Invalid CSRF token" or "Your session has expired", the message is misleading. It almost never means CSRF protection is genuinely blocking the call — it usually means the authentication flow itself failed and ServiceNow silently redirected the request to a login page.

What makes this confusing is that the same integration often works fine against one instance and fails in another, which sends both teams chasing the wrong root cause.

This article explains why that happens and how to diagnose and fix it on current ServiceNow releases.

What actually causes "CSRF" errors in integrations

A ServiceNow CSRF-style error on an integration call almost always comes down to one of two things.

1. ServiceNow treats the request as a browser session instead of an API call. If the request hits a UI-oriented endpoint, or is missing headers that mark it as API traffic, ServiceNow applies session/CSRF validation rules meant for the browser.

2. The authentication flow fails and redirects. When OAuth token exchange fails, ServiceNow can redirect to a login page instead of returning a JSON error. The external system receives an HTML login page where it expected a token or API response, and that gets surfaced upstream as a session/CSRF error.

A properly authenticated, correctly scoped API call should never trigger a CSRF error. If you're seeing one, treat it as a symptom of an authentication or routing problem, not an actual CSRF violation.

Why AWS Glue (or similar tools) might trigger this

AWS Glue integrates with ServiceNow by performing OAuth authentication, then REST API reads/writes against /api/now/table/ endpoints. When that sequence breaks down, Glue typically surfaces one of the two messages above. Either one means ServiceNow did not accept the incoming credentials as valid API traffic and fell back to browser-session handling. The real causes are almost always one of the following.

1. Wrong or deprecated OAuth grant type

This is the most common root cause, and it has shifted in the last couple of release cycles. The current recommendation is to use the Client Credentials grant (client ID + client secret only, no end-user credentials) for machine-to-machine integrations like Glue. This is what ServiceNow documents as the correct pattern for service-to-service API access.

A note on ROPC: Resource Owner Password Credentials — the grant type that also requires a username and password — still exists on the platform, but it's now explicitly discouraged. It's deprecated under the OAuth 2.1 specification, and ServiceNow added a system property, glide.oauth.inbound.ropc.grant_type.disabled, that instance admins can (and increasingly do) set to true to block it outright.

If your instance has that property enabled and Glue (or its OAuth app registration) is still configured for ROPC, every token request will fail, ServiceNow will redirect toward a login page, and Glue will report it as a CSRF/session error — even though the actual cause is a disabled grant type. Check the OAuth Transaction Log for a disabled_grant_type error specifically; it's a fast way to confirm this cause.

2. OAuth client ID mismatch across environments

Each ServiceNow instance (dev, test, prod) has its own OAuth Application Registry entry and therefore its own client ID and secret, even for "the same" integration. A common failure pattern:

  • Correct client ID configured for DEV
  • Correct client ID configured for PROD
  • Stale or incorrect client ID left over in TEST

Authentication fails silently against the mismatched environment, ServiceNow falls back to the login URL, and the resulting error looks like a CSRF failure rather than what it is — a credentials mismatch.

3. Identity provider "External Logout Redirect" misconfiguration

If your instance uses an external identity provider, check the External Logout Redirect setting. When OAuth authentication fails, some configurations redirect to this URL instead of returning a proper error response. Glue then receives an HTML "session expired" page instead of JSON, and that gets interpreted downstream as a CSRF failure. The fix is usually to correct the redirect target, not to touch CSRF settings at all.

4. The integration user account is locked, inactive, or has an expired password

If the integration relies on a specific ServiceNow user account (common with ROPC, but also relevant if a service account is tied to token issuance), that account has to be active, unlocked, and — if ROPC is somehow still in play — have a valid, non-expired password. If the account is locked in one environment but not another, you'll see the integration work everywhere except that one instance, which is exactly the confusing pattern this article opened with.

5. Instance-specific redirect or SSO policy differences

One instance may simply have stricter security posture than another — a tighter SSO policy, more restrictive login rules, or IP allowlisting that the integration's egress IPs don't match. Any of these can cause OAuth to fail in one environment while working fine in another with an otherwise identical configuration.

6. The API endpoint is being treated as a UI page

If the integration is pointed at a UI-facing URL instead of a proper REST endpoint under /api/now/ — even something as small as a missing /now segment — ServiceNow applies browser session rules and enforces CSRF validation. Double-check the full endpoint path, not just the base instance URL.

7. OAuth scope mismatch

If the OAuth Application Registry on the target instance defines a specific set of scopes and Glue's OAuth client is configured to request a scope that isn't registered there, the token request is rejected and ServiceNow redirects to login — again surfacing as "session expired." This is worth checking specifically when an integration works in one instance and not another with a similarly named but differently scoped OAuth registration.

How to diagnose this systematically

Step 1 — Check the OAuth Transaction Logs. Navigate to System OAuth > Application Registry, open the relevant entry, and review its OAuth Transaction Logs. Failed transactions will show the actual cause directly: wrong or disabled grant type, invalid client ID, unexpected redirect, or a specific authentication error code.

Step 2 — Verify the integration user, if a user-based flow is involved. Check that the account is active, not locked, and — if a password-based flow is somehow still configured — that the password hasn't expired.

Step 3 — Compare the OAuth Application Registry across instances. Check these fields side by side between the working and failing instance:

  • OAuth application name
  • Client ID
  • Client Secret
  • Token URL
  • Redirect URL
  • Grant type(s) enabled
  • Scopes
  • Whether glide.oauth.inbound.ropc.grant_type.disabled is set differently between instances

When an integration works in one instance and fails in another, at least one of these fields is the difference.

Step 4 — Get logs from the external tool. Ask the Glue (or equivalent) team for their request/response logs. You're looking for whether the token endpoint returned JSON or an HTML login page, what the actual redirect URL was, and the raw HTTP status code. If they got a login page instead of a token or API response, the OAuth flow — not CSRF — is the problem.

Step 5 — Confirm the correct endpoints are being called. The token request should go to:

https://<instance>.service-now.com/oauth_token.do

and API calls should be scoped under:

/api/now/table/

Anything else gets treated under browser/UI rules, which is where CSRF enforcement kicks in.

Final thoughts

CSRF-labeled errors in ServiceNow integrations are almost always a downstream symptom of an OAuth authentication or configuration issue — not an actual CSRF violation. On current releases, that most often traces back to a deprecated or disabled ROPC grant type, since ServiceNow has been actively pushing instances toward Client Credentials for machine-to-machine integrations.

Work through grant type, client ID/secret accuracy, scopes, redirect configuration, integration user status, and endpoint URLs in that order, and you'll typically isolate the real cause — and get the integration back online — within minutes rather than hours.


This article reflects current ServiceNow guidance on OAuth 2.0 authentication for inbound integrations, including the ongoing deprecation of the Resource Owner Password Credentials grant type. Exact system property names and default behavior can vary by release family — verify against your instance's OAuth Transaction Logs and System Properties before making changes.

Friday, June 20, 2025

Handling Common API Errors and Timeouts When Connecting to ServiceNow

ServiceNow common API errors and timeouts

Integrating PowerShell with the ServiceNow Table API mostly works — right up until a large table like incident, task, or cmdb_ci throws something unexpected: a script that crashes with no clear reason, a response that's missing fields you expected, or a call that just times out. This article covers the real causes behind the most common failures, and — just as importantly — the actual HTTP behavior involved, since getting that wrong leads to error-handling code that silently doesn't work.

Problem 1: "Transaction Cancelled – Maximum Execution Time Exceeded"

This happens when the server-side query takes too long to process — commonly on large tables, broad queries, or when heavy business logic runs on every matched record. The actual response comes back as HTTP 500 Internal Server Error, with this JSON body:

{
  "error": {
    "message": "Transaction cancelled: maximum execution time exceeded",
    "detail": "Transaction cancelled: maximum execution time exceeded Check logs for error trace or enable glide.rest.debug property to verify REST request processing"
  },
  "status": "failure"
}

This comes back as a real HTTP 500 error, not a 200. That distinction matters for how you handle it in PowerShell, covered in Problem 3 below. The default timeout is controlled by the glide.rest.max_execution_time_in_seconds property (60 seconds out of the box).

Why This Happens

  • You're pulling too many records at once.
  • Filters use unindexed or dot-walked fields (see Problem 2).
  • Long-running Business Rules or Flows are slowing down the underlying query.
  • You forgot to paginate or narrow the date range.

Fix It With PowerShell

# Correct use of pagination and fields
$limit = 100
$offset = 0
$url = "https://$instance.service-now.com/api/now/table/incident?sysparm_limit=$limit&sysparm_offset=$offset&sysparm_fields=number,short_description,state"

$response = Invoke-RestMethod -Uri $url -Headers $headers
$response.result

Tip: always use sysparm_limit, sysparm_offset, and sysparm_fields together to reduce payload size and processing time per request — smaller, paginated calls are far less likely to hit the execution time ceiling than one large unbounded query.

Problem 2: Dot-Walked Field Filters Kill Performance

A filter like this:

caller_id.department.name=Finance

...is slow and prone to timing out, because dot-walking through reference fields in a query introduces implicit joins under the hood. The deeper the dot-walk, the more expensive the query gets.

Fix

Use direct sys_id values instead of dot-walking through display names:

$filter = "caller_id=6816f79cc0a8016401c5a33be04be441"
$url = "https://$instance.service-now.com/api/now/table/incident?sysparm_query=$filter"

If you don't already know the sys_id you need, resolve it with a separate, targeted lookup first, rather than filtering the large table by a dot-walked value on every call.

Problem 3: Handling Real Errors in PowerShell Correctly

Since a timeout or server error comes back as a real HTTP 500 (or 400, 403, 404, depending on the failure), Invoke-RestMethod treats it as a terminating error by default in Windows PowerShell. That means an unhandled call doesn't quietly continue with an empty result — it stops your script entirely at that line, which is its own problem if you're not expecting it.

Add Proper Error Handling

try {
    $response = Invoke-RestMethod -Uri $url -Method Get -Headers $headers -ErrorAction Stop
    Write-Host "✅ Records returned: $($response.result.Count)"
}
catch {
    $statusCode = $_.Exception.Response.StatusCode.value__
    $errorBody = $_.ErrorDetails.Message
    Write-Host "❌ API call failed with status $statusCode"
    Write-Host "Details: $errorBody"
}

On PowerShell 7+, Invoke-RestMethod also supports -SkipHttpErrorCheck, which lets you inspect $response.StatusCode directly without throwing — useful if you want to branch on specific status codes without a try/catch block:

$response = Invoke-RestMethod -Uri $url -Headers $headers -SkipHttpErrorCheck -StatusCodeVariable statusCode
if ($statusCode -ne 200) {
    Write-Host "❌ API Error ($statusCode): $($response.error.message)"
} else {
    Write-Host "✅ Records returned: $($response.result.Count)"
}

Problem 4: A 200 That Still Isn't What You Expected

There is a real scenario where a genuinely successful 200 OK response doesn't give you what you expected: Access Control Rules silently filtering data out of the response. If the account making the API call doesn't have read access to certain fields or records, ServiceNow doesn't return an error for that — it simply omits or blanks out what the caller isn't allowed to see. The call succeeds, the status is 200, and the response can still look "off" — fewer records than expected, or fields present in the schema but empty in every row.

If a script consistently returns fewer records or thinner data than expected with no error at all, checking the calling account's role and ACL access on the target table is usually a faster diagnosis than assuming it's a scripting bug.

Extra Debugging Tools

  • Enable REST logs in ServiceNow. Set glide.rest.debug = true temporarily (turn it back off afterward — this is verbose and not meant to run permanently).
  • Check syslog for REST errors. Navigate to System Logs > Errors.
  • Use Postman to isolate the failure. If a call fails from PowerShell but succeeds with identical parameters in Postman, the problem is in your PowerShell handling — headers, encoding, or error handling — not the API call itself.

Conclusion

Performance and error handling both matter here, but get the mechanics right first: ServiceNow's REST Table API returns real, standard HTTP status codes for real errors — it doesn't hide a 500-level failure behind a 200. The practical risk in PowerShell isn't a disguised error slipping past your checks; it's an unhandled terminating exception stopping your script cold, or an ACL quietly limiting what a 200 actually contains. Handle both, and paginate your queries with sysparm_limit/sysparm_offset/sysparm_fields, and most of what shows up here goes away.

Getting Started – PowerShell + ServiceNow Table API with Authentication

Introduction

ServiceNow's Table API provides full CRUD access to any record in the platform. Combine that with PowerShell, and you unlock the ability to automate ticketing, compliance tracking, CMDB updates, and more — right from the command line.

In this article, you'll learn how to:

  • Authenticate using Basic Auth, OAuth2 password grant (and why to avoid it), and OAuth2 Client Credentials grant (the current recommended approach)
  • Make your first Table API call from PowerShell
  • Parse and handle JSON responses
  • Set the stage for advanced integration in later posts

1. Basic Authentication Setup

This is the simplest approach, but it's not recommended for production. Basic Auth sends credentials with every single request, which is why it's generally treated as a legacy pattern for anything beyond quick local testing.

# Replace with your instance and credentials
$instance = "dev12345"
$user = "admin"
$pass = "your_password"
$base64Auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$user`:$pass"))

# Define headers and URL
$headers = @{
    "Authorization" = "Basic $base64Auth"
    "Accept"        = "application/json"
}
$url = "https://$instance.service-now.com/api/now/table/incident?sysparm_limit=1"

# Call the API
$response = Invoke-RestMethod -Uri $url -Method Get -Headers $headers

# Output the result
$response.result

⚠️ Tip: Avoid hardcoding passwords, even for quick testing. Basic Auth is fine for a five-minute experiment against a Personal Developer Instance — don't build anything real on top of it.

2. OAuth2 Password Grant — and Why to Avoid It for New Work

This is the grant type shown in most older ServiceNow + PowerShell tutorials, including earlier drafts of this one. It's worth understanding, but it's no longer the right choice for anything new:

Prerequisites:

  • OAuth enabled in ServiceNow
  • A registered app with Client ID and Client Secret
  • A user account with API access
# Credentials and endpoint
$clientId = "your_client_id"
$clientSecret = "your_client_secret"
$username = "admin"
$password = "your_password"
$instance = "dev12345"
$tokenUrl = "https://$instance.service-now.com/oauth_token.do"

# Build request body
$body = @{
    grant_type = "password"
    client_id = $clientId
    client_secret = $clientSecret
    username = $username
    password = $password
}

# Get token
$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"
$accessToken = $response.access_token

# Make Table API request
$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Accept"        = "application/json"
}
$url = "https://$instance.service-now.com/api/now/table/incident?sysparm_limit=1"
$data = Invoke-RestMethod -Uri $url -Method Get -Headers $headers

$data.result

Why this isn't actually the secure upgrade it looks like: this is the Resource Owner Password Credentials ("password") grant — and it's explicitly deprecated per current OAuth 2.0 security guidance, not just an older option among equals. Notice that it still requires the literal user password in the request body, which means switching from Basic Auth to this grant type doesn't actually solve the "avoid hardcoding passwords" problem — it just moves the same password from a request header into a request body. If you're on an older instance that doesn't yet support the grant type below, this remains a working fallback. For anything new, use Client Credentials instead.

3. OAuth2 Client Credentials Grant (Recommended)

Available since the Washington DC release, this grant is purpose-built for exactly this scenario — a script or service authenticating as itself, with no specific human user's password involved at all.

Prerequisites:

  • OAuth enabled in ServiceNow
  • A registered application configured for the Client Credentials grant, with an associated OAuth Application User (a dedicated service account used as the identity context for the token — configure this with the "Web service access only" / "Internal Integration User" option so it can't log into the UI and isn't subject to normal password expiration policies)
  • On Zurich and later, register this through System OAuth > Application Registry > New Inbound Integration Experience > New Integration > OAuth – Client Credentials grant. On older releases, use Create an OAuth API endpoint for external clients instead — the underlying mechanism is the same, just a different registration screen.
# Credentials and endpoint — no username or password needed
$clientId = "your_client_id"
$clientSecret = "your_client_secret"
$instance = "dev12345"
$tokenUrl = "https://$instance.service-now.com/oauth_token.do"

# Build request body
$body = @{
    grant_type    = "client_credentials"
    client_id     = $clientId
    client_secret = $clientSecret
}

# Get token
$response = Invoke-RestMethod -Uri $tokenUrl -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"
$accessToken = $response.access_token

# Make Table API request
$headers = @{
    "Authorization" = "Bearer $accessToken"
    "Accept"        = "application/json"
}
$url = "https://$instance.service-now.com/api/now/table/incident?sysparm_limit=1"
$data = Invoke-RestMethod -Uri $url -Method Get -Headers $headers

$data.result

Notice what's missing compared to the password grant: no username, no password. The script authenticates as the registered application itself, using the Client ID and Client Secret only. The Client Secret is still a credential worth protecting properly — that part of the "avoid hardcoding secrets" advice still applies, and we'll cover secure secret handling in Part 4 — but you're no longer storing an actual human user's password anywhere in the integration at all, which meaningfully reduces what's at risk if the script or its configuration ever leaks.

4. What You Should See

A single incident record, structured in JSON, regardless of which authentication method above you used:

{
  "result": [
    {
      "number": "INC0010001",
      "short_description": "Sample Incident",
      "state": "1",
      "sys_id": "abc123..."
    }
  ]
}

Conclusion

Connecting PowerShell to ServiceNow via the Table API is a powerful step toward automation. Whether you're managing incidents, risks, or CMDB items, understanding authentication methods — and which ones are actually current — is key. Basic Auth is fine for a quick local test; the password grant still works but is on its way out; Client Credentials is the one worth building on for anything you intend to keep running.

In future articles, we'll make this integration enterprise-grade with error handling, secure credential storage, and better performance.