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 22 hook points across the request lifecycle, listed 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:
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
}
The exact function signature varies by hook point. Some hooks omit the rw or req parameters, some return an error. Refer to the SDK documentation for each hook’s expected signature.
Configuration
Each service extension hook accepts a ServiceExtension object with the following structure:
someHookSE:
funcName: MyFunction
file: /path/to/extension.go
metadata:
key: value
You can provide code inline instead of referencing a file:
someHookSE:
funcName: MyFunction
code: |
package main
import "net/http"
func MyFunction(api struct{ ... }) error {
// custom logic
return nil
}
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.
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 | api.TAI() | Trusted AI identity operations (NewSignedJWT) |
For complete interface definitions, method signatures, and type details, see the SDK reference on pkg.go.dev.
Hook Points
The Orchestrator provides 22 service extension hook points organized by lifecycle area. Each hook is a field on the relevant configuration object.
API Lifecycle
| Hook | Config Location | Purpose |
|---|
serveSE | apis[].serveSE | Handle custom API endpoint requests |
See Custom API Endpoints for the apis[] configuration.
Proxy App Lifecycle
These hooks are available on apps with type: proxy:
| Hook | Config Location | Purpose |
|---|
loadAttrsSE | apps[].loadAttrsSE | Load custom attributes before request processing |
handleUnauthorizedSE | apps[].handleUnauthorizedSE | Customize the unauthorized response |
modifyRequestSE | apps[].modifyRequestSE | Modify the request before proxying to the upstream |
modifyResponseSE | apps[].modifyResponseSE | Modify the response before returning to the client |
createHeaderSE | apps[].headers[].createHeaderSE | Generate custom header values |
isAuthenticatedSE | apps[].policies[].authentication.isAuthenticatedSE | Custom authentication check |
authenticateSE | apps[].policies[].authentication.authenticateSE | Custom authentication flow |
isLoggedInSE / loginSE | apps[].upstreamLogin.isLoggedInSE / apps[].upstreamLogin.loginSE | Upstream application login automation |
OIDC Provider App Lifecycle
These hooks are available on apps with type: oidc:
| Hook | Config Location | Purpose |
|---|
loadAttrsSE | apps[].loadAttrsSE | Load custom attributes before token issuance |
buildIDTokenClaimsSE | apps[].buildIDTokenClaimsSE | Customize ID token claims |
buildAccessTokenClaimsSE | apps[].buildAccessTokenClaimsSE | Customize access token claims |
isAuthenticatedSE | apps[].authentication.isAuthenticatedSE | Custom authentication check |
authenticateSE | apps[].authentication.authenticateSE | Custom authentication flow |
OIDC Provider Level
| Hook | Config Location | Purpose |
|---|
buildUserInfoClaimsSE | oidcProvider.buildUserInfoClaimsSE | Customize the UserInfo endpoint response |
SAML Provider App Lifecycle
These hooks are available on apps with type: saml:
| Hook | Config Location | Purpose |
|---|
loadAttrsSE | apps[].loadAttrsSE | Load custom attributes before assertion building |
buildClaimsSE | apps[].buildClaimsSE | Customize SAML assertion claims |
Authorization
| Hook | Config Location | Purpose |
|---|
isAuthorizedSE | apps[].policies[].authorization.isAuthorizedSE or apps[].authorization.isAuthorizedSE | Custom authorization logic |
Session Lifecycle
| Hook | Config Location | Purpose |
|---|
evalIdleTimeoutSE | session.lifetime.evalIdleTimeoutSE | Dynamically evaluate session idle timeout |
evalMaxLifetimeSE | session.lifetime.evalMaxLifetimeSE | Dynamically evaluate session max lifetime |
Single Logout
| Hook | Config Location | Purpose |
|---|
postLogoutSE | singleLogout.postLogout.postLogoutSE | Custom behavior after single logout completes |
Backchannel Authentication
| Hook | Config Location | Purpose |
|---|
authenticateSE | apps[].authentication.backchannel.authenticateSE | Backchannel authentication (e.g., Resource Owner Password Credentials) |
Custom API Endpoints
Custom API endpoints let you define your own HTTP endpoints served by the Orchestrator, powered by service extensions. Each endpoint is defined under the apis top-level key and handled by a service extension function that receives the full Orchestrator interface.
Configuration
apis:
- name: my-custom-endpoint
serveSE:
funcName: ServeAPI
file: /path/to/api-handler.go
Field Reference
| Key | Type | Default | Required | Description |
|---|
apis[].name | string | — | Yes | Unique name for the API endpoint |
apis[].serveSE | ServiceExtension | — | Yes | Service extension that handles requests to this endpoint |
The serveSE field accepts the standard ServiceExtension object (funcName, code/file, metadata, goPath, allowedProtectedPackages).
Validation Rules
- Each
name must be unique across all apis[] entries
- The
serveSE field is required — an API endpoint without a handler is invalid
Example
A custom health check endpoint and a user info endpoint:
apis:
- name: custom-health
serveSE:
funcName: HealthCheck
file: /etc/maverics/extensions/health-check.go
metadata:
backendURL: "https://backend.example.com/health"
- name: user-info
serveSE:
funcName: GetUserInfo
file: /etc/maverics/extensions/user-info.go
Multiple custom APIs can be defined, each with its own service extension handler.
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:
package main
import (
"net/http"
"github.com/strata-io/service-extension/orchestrator"
)
func LoadCustomAttrs(api orchestrator.Orchestrator, rw http.ResponseWriter, req *http.Request) {
log := api.Logger()
attrs, err := api.AttributeProvider("ldap").Query("", []string{"mail", "displayName", "memberOf"})
if err != nil {
log.Error("se", "failed to query attributes", "error", err.Error())
return
}
api.Session().SetString("email", attrs["mail"])
api.Session().SetString("displayName", attrs["displayName"])
api.Session().SetJSON("groups", attrs["memberOf"])
if err := api.Session().Save(); err != nil {
log.Error("se", "failed to save session", "error", err.Error())
}
}
Reference the extension from your YAML configuration:
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 — trusted AI identity
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:
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.
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.
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.
apiKey, err := api.SecretProvider().GetString("my-api-key")
if err != nil {
api.Logger().Error("se", "failed to retrieve API key", "error", err.Error())
return
}
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.
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:
- 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.
- Deploy to staging — deploy the extension to a non-production Orchestrator instance connected to test identity providers and backend services.
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.
- Verify behavior — use structured logging (
api.Logger()) to trace extension execution. Check Orchestrator logs for errors, unexpected behavior, or performance issues.
- Promote to production — after verifying correct behavior in staging, deploy the extension to production. Monitor Orchestrator logs after deployment for errors from extension code.
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.
Related Pages