OutSystems supports three main API authentication patterns: Basic Auth (built-in toggle), API Key (Custom authentication + OnAuthentication callback), and OAuth2 (built-in client credentials flow for consumed APIs, manual for exposed). Store all credentials in Site Properties (O11) or Secret Settings (ODC) — never hardcode them. Use the OnBeforeRequest callback to inject tokens dynamically into outgoing requests.
Securing API Integrations in OutSystems
Authentication is the first line of defence for any API integration. OutSystems provides built-in hooks for the most common patterns: Basic Auth, API keys in custom headers, and OAuth2 client credentials. Whether you are calling an external API from your app or exposing your own endpoints to the world, the same principles apply — credentials belong in environment-specific configuration, never in source code, and validation logic belongs in callbacks that run on every request. This tutorial covers both sides: securing outgoing calls and validating incoming ones.
Prerequisites
- A consumed REST API already imported (Logic tab → Integrations → REST) or an exposed REST API node created
- An entity to store API keys (for exposed API key validation) — e.g., ApiKey entity with KeyValue (Text), IsActive (Boolean), ExpiresOn (DateTime)
- Site Property (O11) or Secret Setting (ODC) for storing credentials — Data tab → Site Properties → Add Site Property
- Basic familiarity with OutSystems Server Actions and exception handling
Step-by-step guide
Store credentials in Site Properties or Secret Settings — never in code
Store credentials in Site Properties or Secret Settings — never in code
Before writing any authentication logic, create secure storage for your credentials. O11: Go to Data tab → Site Properties → right-click → Add Site Property. Create: - ApiBaseUrl (Text) — the external API base URL - ApiClientId (Text) — OAuth2 client ID - ApiClientSecret (Text) — OAuth2 client secret (mark as Secret: Yes) - ApiKey (Text) — static API key (mark as Secret: Yes) Set default values and configure per-environment values in Service Center → Factory → [Module] → Site Properties. ODC: In ODC Portal, navigate to your App → Settings → Secret Settings. Create the same keys. Secret Settings are encrypted at rest and never visible after saving. Access them in action flows via the GetSetting system action.
Expected result: Credentials are stored in environment-specific configuration, accessible in Server Actions without hardcoding.
Configure Basic Authentication for a consumed REST API
Configure Basic Authentication for a consumed REST API
Open your consumed REST API node under Logic tab → Integrations → REST. In the Properties panel, find the On Before Request property. If the external API uses Basic Auth (username:password encoded in the Authorization header), you have two options: Option A — Method-level header (simple): In the REST method dialog, Headers/Auth tab → Add Header → Name: Authorization, Value: "Basic " + ToBase64(Username + ":" + Password). This is static — only use for dev/test. Option B — OnBeforeRequest callback (recommended for production): Set the On Before Request property to New OnBeforeRequest. Inside the callback action: Start → GetSiteProperty (ApiUsername) → GetSiteProperty (ApiPassword) → Assign (Request.Headers append Authorization = "Basic " + ToBase64(ApiUsername + ":" + ApiPassword)) → Assign CustomizedRequest = Request → End
Expected result: Every outgoing request to this API includes the correct Basic Auth header, sourced from Site Properties.
Configure OAuth2 client credentials for a consumed REST API
Configure OAuth2 client credentials for a consumed REST API
For APIs using OAuth2 machine-to-machine (client credentials flow): O11 built-in support: 1. Click your consumed REST API node in Logic tab → Integrations → REST 2. In Properties panel, set Authentication to OAuth 2.0 3. Fill in: Authorization Server URL (the token endpoint), Client Id, Client Secret, Scopes 4. Service Studio handles token fetching and refresh automatically Advanced pattern (works in both O11 and ODC — use when built-in doesn't support your IdP's token format): 1. Create a Server Action FetchOAuthToken 2. Inside, call a REST method POST to the token endpoint with body: grant_type=client_credentials&client_id={Id}&client_secret={Secret}&scope={Scopes} 3. Parse the access_token from the JSON response 4. Store the token in a Site Property or cache entity with ExpiresAt timestamp 5. In OnBeforeRequest: fetch the cached token, check if expired → refresh if needed → inject into Authorization header Action flow for OnBeforeRequest: Start → GetCachedToken → If (Token.ExpiresAt < CurrDateTime()) → [True] FetchOAuthToken → UpdateCachedToken → [False] (proceed) → Assign (Request.Headers append Authorization = "Bearer " + Token.AccessToken) → Assign CustomizedRequest = Request → End
Expected result: Your app fetches and caches OAuth2 tokens automatically, refreshing them before expiry.
Implement API key validation for an exposed REST API
Implement API key validation for an exposed REST API
To protect your exposed REST API with an API key: 1. Click your exposed REST API node (Logic tab → Integrations → REST → your API) 2. In Properties panel, set Authentication to Custom 3. An OnAuthentication action is automatically added under your REST API node Open the OnAuthentication action and build this flow: Start → GetRequestHeader (Name = "X-API-Key") → GetApiKeyRecord [Aggregate: ApiKey entity, Filter: ApiKey.KeyValue = GetRequestHeader.Value AND ApiKey.IsActive = True AND ApiKey.ExpiresOn > CurrDateTime()] → If (GetApiKeyRecord.List.Empty) → [True] Raise Exception (InvalidApiKey User Exception) → [False] End The raised InvalidApiKey User Exception automatically causes OutSystems to return HTTP 403 Forbidden to the caller. No additional response-writing is needed.
Expected result: API calls without a valid X-API-Key header receive HTTP 403. Calls with a valid key proceed to the method handler.
Implement Bearer token validation for an exposed REST API
Implement Bearer token validation for an exposed REST API
For token-based authentication on your exposed API: 1. Set Authentication to Custom on the exposed REST API node (same as step 4) 2. Open the OnAuthentication action Action flow: Start → GetRequestHeader (Name = "Authorization") → Assign (BearerToken = Replace(AuthHeader, "Bearer ", "")) → ValidateToken [Server Action or Aggregate that verifies the token against your token store or calls an OIDC introspection endpoint] → If (IsTokenValid = False) → [True] Raise Exception (UnauthorizedAccess Security Exception) → [False] Assign (CurrentUserId = TokenPayload.UserId) → End For JWT tokens, use the JWT Forge component (Logic tab → Manage Dependencies → search JWT). The ValidateJWT action verifies the signature and expiry. Extract claims (sub, roles, exp) from the validated token. TrueChange note: Raising a Security Exception from OnAuthentication causes OutSystems to return HTTP 401 Unauthorized — appropriate for token validation failures.
Expected result: Requests with invalid or expired tokens receive HTTP 401. Valid token requests include the authenticated user context for use in downstream logic.
Test authentication end to end
Test authentication end to end
After publishing (Ctrl+F5), test both positive and negative cases: Using the Swagger UI (right-click exposed REST API → Open Documentation): 1. Click Authorize in the Swagger UI 2. Enter your API key or Bearer token 3. Execute a method — verify 200 response 4. Remove or corrupt the credential — verify 401 or 403 response For consumed APIs, add a Test Server Action that calls your imported method and trace the execution in the Service Studio Debugger: 1. Set a breakpoint on the Aggregate or GetProperty node 2. Press Debug (F6) 3. Step through to verify the Authorization header is correctly set in the OnBeforeRequest output Check Service Center → Monitoring → Integrations for logs of outgoing REST calls including request headers and response status codes.
Expected result: Authentication works correctly in both directions. Logs in Service Center confirm correct headers are being sent/received.
Complete working example
1/* OnBeforeRequest Action — OAuth2 Bearer Token Injection2 Attached to a consumed REST API node.3 Input: Request (HTTPRequest)4 Output: CustomizedRequest (HTTPRequest)5*/67Start8 |9 v10[GetCachedTokenRecord] -- Aggregate11 Source: OAuthTokenCache entity12 Filter: OAuthTokenCache.ApiName = "ExternalAPI"13 MaxRecords: 114 |15 v16[If] Condition: GetCachedTokenRecord.List.Empty17 OR GetCachedTokenRecord.List.Current.OAuthTokenCache.ExpiresAt <= AddSeconds(CurrDateTime(), 60)18 |19 |-- [True: token missing or expiring soon] ->20 | [FetchNewToken] -- Server Action calling token endpoint21 | ClientId = Site.OAuthClientId22 | ClientSecret = Site.OAuthClientSecret23 | TokenUrl = Site.OAuthTokenUrl24 | Scope = Site.OAuthScope25 | Returns: AccessToken (Text), ExpiresIn (Integer)26 | |27 | [CreateOrUpdateOAuthTokenCache]28 | ApiName = "ExternalAPI"29 | Token = FetchNewToken.AccessToken30 | ExpiresAt = AddSeconds(CurrDateTime(), FetchNewToken.ExpiresIn - 30)31 | |32 | [Assign] BearerToken = FetchNewToken.AccessToken33 |34 |-- [False: valid cached token] ->35 [Assign] BearerToken = GetCachedTokenRecord.List.Current.OAuthTokenCache.Token36 |37 v38[Assign] -- Build header39 NewHeader.Name = "Authorization"40 NewHeader.Value = "Bearer " + BearerToken41 |42 v43[ListAppend] Request.Headers += NewHeader44 |45 v46[Assign] CustomizedRequest = Request47 |48 v49EndCommon mistakes
Why it's a problem: Hardcoding API keys or client secrets directly in the REST method URL or headers fields in Service Studio
How to avoid: Move all credentials to Site Properties (O11) or Secret Settings (ODC). Use the OnBeforeRequest callback to read the credential at runtime and inject it into the request header. The credential is then configurable per environment.
Why it's a problem: Using Authentication = None on an exposed REST API in a production environment
How to avoid: Set Authentication to Custom and implement an OnAuthentication action that validates every inbound request. An unauthenticated exposed API with no IP restrictions is accessible to the entire internet.
Why it's a problem: Fetching a new OAuth2 token on every API call instead of caching it
How to avoid: Cache the token in an entity or Site Property with an ExpiresAt timestamp. In OnBeforeRequest, only fetch a new token when the cached one is missing or expiring. Fetching a new token on every call adds 200-500ms latency and may hit the auth server's rate limits.
Why it's a problem: Raising a User Exception instead of a Security Exception for authentication failures in OnAuthentication
How to avoid: User Exceptions return HTTP 400 Bad Request. Authentication failures should return HTTP 401 or 403. Raise a Security Exception for authentication/authorization failures so OutSystems returns the semantically correct HTTP status code.
Best practices
- Always store API keys, client secrets, and tokens in Site Properties (O11) with Secret = Yes, or in Secret Settings (ODC) — never in code, entity records, or plain Site Properties.
- Cache OAuth2 access tokens with an expiry timestamp and refresh them proactively (60 seconds before they expire) to avoid failed requests during token boundary transitions.
- Use the OnBeforeRequest callback to inject authentication headers rather than hardcoding them in method URL headers — callbacks can read from secure Site Properties dynamically.
- For exposed APIs, store API keys with an IsActive flag and ExpiresOn datetime so you can revoke or rotate keys without redeploying the module.
- Raise a Security Exception (not a User Exception) for authentication failures in OnAuthentication — this returns HTTP 401 rather than 400, which is the correct semantics.
- Log authentication failures (invalid tokens, expired keys) to Service Center using the LogError system action — include the remote IP from the request headers for security monitoring.
- Rotate API keys and OAuth credentials on a schedule (every 90 days is a common standard) by updating Site Property values in Service Center without redeploying.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm working with OutSystems 11 and need to call an external API that uses OAuth2 client credentials flow. The token endpoint is POST https://auth.example.com/oauth/token with body params: grant_type, client_id, client_secret, scope. The access token expires in 3600 seconds. Help me design: 1) the Site Properties to store credentials, 2) the Server Action to fetch and cache the token, 3) the OnBeforeRequest action to inject the Bearer token into every API call.
In my OutSystems module, I have an exposed REST API named v1 and I want to protect it with API key authentication using a custom header X-API-Key. I have an ApiKey entity with attributes: Id (Long Integer), KeyValue (Text), ClientName (Text), IsActive (Boolean), ExpiresOn (DateTime). Write the complete OnAuthentication action flow using arrow notation that validates the API key, handles expired keys separately from invalid keys, and logs all rejected attempts.
Frequently asked questions
How do I handle token refresh for OAuth2 in OutSystems when the access token expires mid-session?
Implement the token cache pattern in your OnBeforeRequest callback: store the token and its expiry in a Site Property or dedicated entity. Before injecting the token, check if it expires within the next 60 seconds — if so, call your FetchNewToken Server Action to get a fresh token before proceeding. This proactive refresh prevents mid-session 401 errors from reaching your users.
Can I use different authentication credentials for different environments (Dev, QA, Prod) without redeploying?
Yes. Store credentials in Site Properties (O11) or Secret Settings (ODC). In O11, go to Service Center → Factory → [Module] → Site Properties and set environment-specific values. The module code always reads from Site Properties at runtime, so the same deployed binary uses different credentials in each environment. In ODC, set environment-specific values in ODC Portal → [Stage] → App → Settings.
What is the difference between how O11 and ODC handle API authentication for exposed APIs?
The OnAuthentication callback pattern works the same in both platforms. The key difference is for external consumers: in ODC, the platform supports OIDC natively — consumers can obtain tokens from ODC Portal → Consumers → API Clients using standard OIDC client credentials flow. In O11, there is no built-in OIDC token server — you must implement token issuance manually or use an external identity provider.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation