> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strata.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Extensions

Service extensions are custom Go functions that you inject at specific hook points in the Orchestrator's request processing pipeline. They use Go syntax but execute within an embedded Go runtime inside the Orchestrator process -- you do not compile or build plugins. Service extension code cannot run independently outside the Orchestrator. Service extensions let you customize authentication, authorization, claims building, routing, session management, and API handling without modifying the Orchestrator itself.

The Orchestrator provides 30 hook points across the request lifecycle, organized by concern in the pages below.

## How Service Extensions Work

When the Orchestrator starts, it loads your Go source code and executes it through an embedded Go runtime. At each configured hook point, the Orchestrator calls your named function, passing an `api` parameter that provides access to the full Orchestrator interface -- sessions, caches, secrets, identity providers, logging, and more.

Service extension code can be delivered in two ways:

* **File reference** -- point to an external `.go` source file on disk. Preferred for production use because it supports version control, IDE tooling, and independent testing.
* **Inline code** -- embed Go source directly in the YAML configuration. Convenient for short, self-contained extensions.

The basic function signature follows this pattern:

```go theme={null}
package main

import (
    "net/http"
    "github.com/strata-io/service-extension/orchestrator"
)

func MyExtension(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
    // Custom logic here
}
```

<Note>
  The exact function signature varies by hook point. Some hooks omit the `rw` or `req` parameters, some return an `error`. Refer to the individual hook pages below or the [SDK documentation](https://pkg.go.dev/github.com/strata-io/service-extension) for each hook's expected signature.
</Note>

## Configuration

Each service extension hook accepts a `ServiceExtension` object with the following structure:

```yaml theme={null}
someHookSE:
  funcName: MyFunction
  file: /path/to/extension.go
  metadata:
    key: value
```

You can provide code inline instead of referencing a file:

```yaml theme={null}
someHookSE:
  funcName: MyFunction
  code: |
    package main

    import (
        "net/http"
        "github.com/strata-io/service-extension/orchestrator"
    )

    func MyFunction(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
        // custom logic
    }
```

### Field Reference

| Key                        | Type             | Default | Required                | Description                                                           |
| -------------------------- | ---------------- | ------- | ----------------------- | --------------------------------------------------------------------- |
| `funcName`                 | string           | --      | Yes                     | Name of the Go function to call                                       |
| `code`                     | string           | --      | One of `code` or `file` | Inline Go source code                                                 |
| `file`                     | string           | --      | One of `code` or `file` | Path to a `.go` source file                                           |
| `metadata`                 | map              | --      | No                      | Key-value metadata passed to the service extension at runtime         |
| `goPath`                   | string           | --      | No                      | Go path for locating additional packages                              |
| `allowedProtectedPackages` | array of strings | `[]`    | No                      | Protected packages to allow (e.g., `"os"`) -- see Runtime Environment |

Either `code` or `file` must be provided, but not both.

## The Service Extension SDK

The Service Extension SDK is a Go module that provides typed interfaces for interacting with Orchestrator services from within your extension code.

* **Module:** [`github.com/strata-io/service-extension`](https://github.com/strata-io/service-extension)
* **Documentation:** [pkg.go.dev/github.com/strata-io/service-extension](https://pkg.go.dev/github.com/strata-io/service-extension)

### SDK Interfaces

The `api` parameter passed to your extension function implements the `orchestrator.Orchestrator` interface, which provides access to all subsystem interfaces:

| Interface                     | Access Via                | Purpose                                                                                                        |
| ----------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------- |
| **Orchestrator**              | `api` parameter           | Main entry point -- provides access to all other interfaces                                                    |
| **Session**                   | `api.Session()`           | Read/write session attributes (GetString, SetString, GetBool, SetBool, GetInt, SetInt, GetJSON, SetJSON, Save) |
| **Logger**                    | `api.Logger()`            | Structured logging with Debug, Info, Error and key-value pairs                                                 |
| **Cache**                     | `api.Cache()`             | Read/write cache entries with TTL (GetBytes, SetBytes)                                                         |
| **SecretProvider**            | `api.SecretProvider()`    | Retrieve secrets from configured providers (Get, GetString)                                                    |
| **IdentityProvider**          | `api.IdentityProvider()`  | Trigger authentication flows (Login, IsAvailable)                                                              |
| **AttributeProvider**         | `api.AttributeProvider()` | Query user attributes from connectors (Query)                                                                  |
| **Router**                    | `api.Router()`            | Register custom HTTP handlers (HandleFunc)                                                                     |
| **HTTP**                      | `api.HTTP()`              | Access HTTP clients for outbound requests (GetClient, SetClient, DefaultClient)                                |
| **App**                       | `api.App()`               | Access current application metadata (Name)                                                                     |
| **Bundle/Assets**             | `api.Bundle()`            | Access bundled static files (FS, ReadFile)                                                                     |
| **TAI/WebLogic** (deprecated) | `api.TAI()`               | WebSphere Trust Association Interceptor operations (NewSignedJWT)                                              |

For complete interface definitions, method signatures, and type details, see the [SDK reference on pkg.go.dev](https://pkg.go.dev/github.com/strata-io/service-extension).

## Hook Points

The Orchestrator provides 30 service extension hook points organized by lifecycle area. Each hook is a field on the relevant configuration object. Select a hook name to see its full signature, parameters, and usage details.

### API Lifecycle

Custom API endpoints are managed through the **Service Extensions** area in the Console. In the Console sidebar, select **Service Extensions** and then **API** to create, edit, and manage custom API endpoints. APIs are attached to deployments from the deployment's **Settings** page alongside applications.

| Hook                                                                | Config Location  | Purpose                             |
| ------------------------------------------------------------------- | ---------------- | ----------------------------------- |
| [`serveSE`](/reference/orchestrator/service-extensions/custom-apis) | `apis[].serveSE` | Handle custom API endpoint requests |

See [Custom APIs](/reference/orchestrator/service-extensions/custom-apis) for the `apis[]` configuration and Console UI setup steps.

### Proxy App Lifecycle

These hooks are available on apps with `type: proxy`:

| Hook                                                                                                                               | Config Location                                                      | Purpose                                            |
| ---------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------- |
| [`isAuthenticatedSE`](/reference/orchestrator/service-extensions/proxy)                                                            | `apps[].policies[].authentication.isAuthenticatedSE`                 | Custom authentication check                        |
| [`authenticateSE`](/reference/orchestrator/service-extensions/proxy)                                                               | `apps[].policies[].authentication.authenticateSE`                    | Custom authentication flow                         |
| [`isAuthorizedSE`](/reference/orchestrator/service-extensions/proxy)                                                               | `apps[].policies[].authorization.isAuthorizedSE`                     | Custom authorization logic                         |
| [`handleUnauthorizedSE`](/reference/orchestrator/service-extensions/proxy)                                                         | `apps[].handleUnauthorizedSE`                                        | Customize the unauthorized response                |
| [`loadAttrsSE`](/reference/orchestrator/service-extensions/proxy)                                                                  | `apps[].loadAttrsSE`                                                 | Load custom attributes before request processing   |
| [`createHeaderSE`](/reference/orchestrator/service-extensions/proxy)                                                               | `apps[].headers[].createHeaderSE`                                    | Generate custom header values                      |
| [`modifyRequestSE`](/reference/orchestrator/service-extensions/proxy)                                                              | `apps[].modifyRequestSE`                                             | Modify the request before proxying to the upstream |
| [`modifyResponseSE`](/reference/orchestrator/service-extensions/proxy)                                                             | `apps[].modifyResponseSE`                                            | Modify the response before returning to the client |
| [`isLoggedInSE`](/reference/orchestrator/service-extensions/proxy) / [`loginSE`](/reference/orchestrator/service-extensions/proxy) | `apps[].upstreamLogin.isLoggedInSE` / `apps[].upstreamLogin.loginSE` | Upstream application login automation              |

### OIDC App Lifecycle

These hooks are available on apps with `type: oidc`:

| Hook                                                                          | Config Location                                    | Purpose                                                                |
| ----------------------------------------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
| [`isAuthenticatedSE`](/reference/orchestrator/service-extensions/oidc)        | `apps[].authentication.isAuthenticatedSE`          | Custom authentication check                                            |
| [`authenticateSE`](/reference/orchestrator/service-extensions/oidc)           | `apps[].authentication.authenticateSE`             | Custom authentication flow                                             |
| [`authenticateSE`](/reference/orchestrator/service-extensions/oidc)           | `apps[].authentication.backchannel.authenticateSE` | Backchannel authentication (e.g., Resource Owner Password Credentials) |
| [`isAuthorizedSE`](/reference/orchestrator/service-extensions/oidc)           | `apps[].authorization.isAuthorizedSE`              | Custom authorization logic                                             |
| [`loadAttrsSE`](/reference/orchestrator/service-extensions/oidc)              | `apps[].loadAttrsSE`                               | Load custom attributes before token issuance                           |
| [`buildIDTokenClaimsSE`](/reference/orchestrator/service-extensions/oidc)     | `apps[].buildIDTokenClaimsSE`                      | Customize ID token claims                                              |
| [`buildAccessTokenClaimsSE`](/reference/orchestrator/service-extensions/oidc) | `apps[].buildAccessTokenClaimsSE`                  | Customize access token claims                                          |

### OIDC Provider Level

| Hook                                                                       | Config Location                      | Purpose                                  |
| -------------------------------------------------------------------------- | ------------------------------------ | ---------------------------------------- |
| [`buildUserInfoClaimsSE`](/reference/orchestrator/service-extensions/oidc) | `oidcProvider.buildUserInfoClaimsSE` | Customize the UserInfo endpoint response |

### SAML App Lifecycle

These hooks are available on apps with `type: saml`:

| Hook                                                                   | Config Location                              | Purpose                                                  |
| ---------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------- |
| [`isAuthenticatedSE`](/reference/orchestrator/service-extensions/saml) | `apps[].authentication.isAuthenticatedSE`    | Custom authentication check (cannot be used with `idps`) |
| [`authenticateSE`](/reference/orchestrator/service-extensions/saml)    | `apps[].authentication.authenticateSE`       | Custom authentication flow (cannot be used with `idps`)  |
| [`isAuthorizedSE`](/reference/orchestrator/service-extensions/saml)    | `apps[].authorization.isAuthorizedSE`        | Custom authorization logic                               |
| [`loadAttrsSE`](/reference/orchestrator/service-extensions/saml)       | `apps[].loadAttrsSE`                         | Load custom attributes before assertion building         |
| [`buildClaimsSE`](/reference/orchestrator/service-extensions/saml)     | `apps[].buildClaimsSE`                       | Customize SAML assertion claims                          |
| [`buildRelayStateSE`](/reference/orchestrator/service-extensions/saml) | `apps[].idpInitiatedLogin.buildRelayStateSE` | Build custom RelayState for IdP-initiated login          |

### LDAP Provider Lifecycle

These hooks are available on the `ldapProvider` configuration:

| Hook                                                                        | Config Location                                                                             | Purpose                                             |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| [`searchSE`](/reference/orchestrator/service-extensions/ldap)               | `ldapProvider.search.searchSE`                                                              | Handle LDAP search operations                       |
| [`authenticateSE`](/reference/orchestrator/service-extensions/ldap)         | `ldapProvider.authentication.methods.simple.authenticateSE`                                 | Authenticate via simple LDAP bind                   |
| [`getHashedCredentialsSE`](/reference/orchestrator/service-extensions/ldap) | `ldapProvider.authentication.methods.sasl.mechanisms.gssspnego.ntlm.getHashedCredentialsSE` | Retrieve hashed credentials for NTLM authentication |

### Session Lifecycle

| Hook                                                                                 | Config Location                      | Purpose                                   |
| ------------------------------------------------------------------------------------ | ------------------------------------ | ----------------------------------------- |
| [`evalIdleTimeoutSE`](/reference/orchestrator/service-extensions/session-and-logout) | `session.lifetime.evalIdleTimeoutSE` | Dynamically evaluate session idle timeout |
| [`evalMaxLifetimeSE`](/reference/orchestrator/service-extensions/session-and-logout) | `session.lifetime.evalMaxLifetimeSE` | Dynamically evaluate session max lifetime |

### Single Logout

| Hook                                                                            | Config Location                        | Purpose                                       |
| ------------------------------------------------------------------------------- | -------------------------------------- | --------------------------------------------- |
| [`postLogoutSE`](/reference/orchestrator/service-extensions/session-and-logout) | `singleLogout.postLogout.postLogoutSE` | Custom behavior after single logout completes |

## Writing a Service Extension

The following example shows a `LoadCustomAttrs` function that queries an attribute provider for user attributes and stores the results in the session:

```go theme={null}
package main

import (
    "net/http"
    "github.com/strata-io/service-extension/orchestrator"
)

func LoadCustomAttrs(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) error {
    log := api.Logger()

    attrProvider, err := api.AttributeProvider("ldap")
    if err != nil {
        log.Error("se", "failed to get attribute provider", "error", err.Error())
        return err
    }

    attrs, err := attrProvider.Query("", []string{"mail", "displayName", "memberOf"})
    if err != nil {
        log.Error("se", "failed to query attributes", "error", err.Error())
        return err
    }

    session, err := api.Session()
    if err != nil {
        log.Error("se", "failed to get session", "error", err.Error())
        return err
    }

    session.SetString("email", attrs["mail"])
    session.SetString("displayName", attrs["displayName"])
    session.SetJSON("groups", attrs["memberOf"])

    if err := session.Save(); err != nil {
        log.Error("se", "failed to save session", "error", err.Error())
        return err
    }

    return nil
}
```

Reference the extension from your YAML configuration:

```yaml theme={null}
apps:
  - name: my-app
    type: proxy
    upstream: https://backend.example.com
    loadAttrsSE:
      funcName: LoadCustomAttrs
      file: /etc/maverics/extensions/load-attrs.go
```

Use `file` for production deployments -- it keeps extension code in version control and enables IDE support. Use inline `code` for short, self-contained extensions where a separate file adds unnecessary overhead.

## Runtime Environment

Service extension code runs through the **Orchestrator's embedded Go runtime**. Code is interpreted at runtime -- there is no compile step.

### Available Packages

By default, service extensions have access to:

* **Go standard library** (except `os`, which is restricted by default)
* **Service Extension SDK** -- packages under `service-extension/` providing access to Orchestrator services:
  * `orchestrator` -- access to the Orchestrator instance (session, cache, secrets, connectors, router)
  * `session` -- read and write session attributes
  * `cache` -- read and write cache entries
  * `secret` -- retrieve secrets from configured secret providers
  * `log` -- structured logging
  * `router` -- request routing
  * `idfabric` -- identity fabric integration
  * `tai` -- WebSphere Trust Association Interceptor (deprecated)
  * `weblogic` -- WebLogic integration
* **Third-party libraries** -- LDAP, JWT, UUID, AWS SDK, HTML parser, NTLM, secp256k1

### Protected Packages

The `os` package is restricted by default to prevent file system and process access. To opt in, add it to `allowedProtectedPackages`:

```yaml theme={null}
someHookSE:
  funcName: ReadConfig
  file: /path/to/extension.go
  allowedProtectedPackages:
    - os
```

## Best Practices

### Error Handling

Always check and handle errors returned by SDK methods. Log errors with sufficient context to diagnose issues in production. A panicking extension can disrupt the Orchestrator's request processing for the affected hook point.

```go theme={null}
attrs, err := api.AttributeProvider("ldap").Query("", []string{"mail"})
if err != nil {
    api.Logger().Error("se", "attribute query failed", "provider", "ldap", "error", err.Error())
    return
}
```

### Logging

Use the SDK logger (`api.Logger()`) instead of `fmt.Println` or the standard `log` package. The SDK logger integrates with the Orchestrator's structured logging pipeline and supports key-value pairs for searchable log entries.

```go theme={null}
log := api.Logger()
log.Info("se", "processing request", "app", api.App().Name(), "user", email)
```

### Secrets Management

Use `api.SecretProvider()` to retrieve secrets at runtime from configured providers (Vault, AWS Secrets Manager, Azure Key Vault, etc.). Never hardcode credentials, API keys, or certificates in extension code.

```go theme={null}
apiKey, err := api.SecretProvider().GetString("my-api-key")
if err != nil {
    api.Logger().Error("se", "failed to retrieve API key", "error", err.Error())
    return
}
```

### Performance

Keep extensions lightweight, especially in hot-path hooks like `modifyRequestSE` and `modifyResponseSE` that execute on every proxied request. Use `api.Cache()` for expensive lookups to avoid redundant calls to external systems.

### HTTP Client Reuse

When making outbound HTTP requests from a service extension, always reuse HTTP clients through the `api.HTTP()` interface rather than creating a new `http.Client` per request. Creating clients per request prevents connection pooling, repeats TLS handshakes, and can lead to socket exhaustion under load.

For most use cases, `api.HTTP().DefaultClient()` provides a ready-to-use client with sensible defaults:

```go theme={null}
func CallAPI(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
    log := api.Logger()

    client := api.HTTP().DefaultClient()

    resp, err := client.Get("https://api.example.com/resource")
    if err != nil {
        log.Error("se", "request failed", "error", err.Error())
        return
    }
    defer resp.Body.Close()

    // Process the response.
}
```

When you need separate clients with different configurations (e.g., distinct timeouts or transport settings for different upstream services), use `api.HTTP().SetClient()` and `api.HTTP().GetClient()` to register and retrieve named clients:

```go theme={null}
func CallAPIs(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
    log := api.Logger()

    // Register a named client once; subsequent calls to GetClient return the same instance.
    _, err := api.HTTP().GetClient("payments-api")
    if err != nil {
        paymentsClient := &http.Client{Timeout: 5 * time.Second}
        api.HTTP().SetClient("payments-api", paymentsClient)
    }

    client, _ := api.HTTP().GetClient("payments-api")
    resp, err := client.Get("https://payments.example.com/charge")
    if err != nil {
        log.Error("se", "payments request failed", "error", err.Error())
        return
    }
    defer resp.Body.Close()

    // Process the response.
}
```

<Note>
  Named clients registered with `SetClient` persist for the lifetime of the Orchestrator process. Use descriptive names (e.g., `"payments-api"`, `"identity-service"`) to avoid collisions between extensions.
</Note>

### Statelessness

Do not rely on global variables or package-level state between requests. The Orchestrator may run multiple instances, and the runtime does not guarantee state persistence across configuration reloads. Use `api.Session()` for request-scoped state and `api.Cache()` for cross-request state.

## Testing and Deployment

Follow a staged workflow to minimize risk when deploying service extensions:

1. **Develop locally** -- write and test your extension code against the SDK interfaces. Use Go tooling (formatting, linting, type checking) during development even though extensions run interpreted.
2. **Deploy to staging** -- deploy the extension to a non-production Orchestrator instance connected to test identity providers and backend services.

<Note>
  **Static analysis limitations:** Because service extensions execute inside the Orchestrator's embedded runtime with injected SDK interfaces, standard static code analysis tools (SAST, linters with security rules) may not produce meaningful results. The most effective way to validate security behavior is to deploy your extension to a test environment and exercise the actual authentication, authorization, and data-handling scenarios your extension participates in.
</Note>

3. **Verify behavior** -- use structured logging (`api.Logger()`) to trace extension execution. Check Orchestrator logs for errors, unexpected behavior, or performance issues.
4. **Promote to production** -- after verifying correct behavior in staging, deploy the extension to production. Monitor Orchestrator logs after deployment for errors from extension code.

<Warning>
  Service extensions execute custom user-authored code within the Orchestrator process. You are responsible for the correctness, security, and performance of your extension code. Strata provides the Service Extension SDK and runtime environment but does not audit, review, or warranty custom extension logic.
</Warning>

## Related Pages

<CardGroup cols={2}>
  <Card title="Applications" icon="browser" href="/reference/orchestrator/applications">
    Application configuration where most SE hooks are defined
  </Card>

  <Card title="Service Extension SDK" icon="github" href="https://github.com/strata-io/service-extension">
    Go module source and API documentation
  </Card>
</CardGroup>
