# REST API Authentication in OutSystems: OAuth2, Bearer Tokens, and API Keys

- Tool: OutSystems
- Difficulty: Intermediate
- Time required: 25-30 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

### 4. 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.

### 5. 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.

### 6. 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 code example

File: `OnBeforeRequest_OAuth2_Token_Injection.txt`

```plaintext
/* OnBeforeRequest Action — OAuth2 Bearer Token Injection
   Attached to a consumed REST API node.
   Input:  Request (HTTPRequest)
   Output: CustomizedRequest (HTTPRequest)
*/

Start
  |
  v
[GetCachedTokenRecord]  -- Aggregate
  Source: OAuthTokenCache entity
  Filter: OAuthTokenCache.ApiName = "ExternalAPI"
  MaxRecords: 1
  |
  v
[If]  Condition: GetCachedTokenRecord.List.Empty
       OR GetCachedTokenRecord.List.Current.OAuthTokenCache.ExpiresAt <= AddSeconds(CurrDateTime(), 60)
  |
  |-- [True: token missing or expiring soon] ->
  |     [FetchNewToken]  -- Server Action calling token endpoint
  |       ClientId     = Site.OAuthClientId
  |       ClientSecret = Site.OAuthClientSecret
  |       TokenUrl     = Site.OAuthTokenUrl
  |       Scope        = Site.OAuthScope
  |       Returns: AccessToken (Text), ExpiresIn (Integer)
  |     |
  |     [CreateOrUpdateOAuthTokenCache]
  |       ApiName   = "ExternalAPI"
  |       Token     = FetchNewToken.AccessToken
  |       ExpiresAt = AddSeconds(CurrDateTime(), FetchNewToken.ExpiresIn - 30)
  |     |
  |     [Assign]  BearerToken = FetchNewToken.AccessToken
  |
  |-- [False: valid cached token] ->
        [Assign]  BearerToken = GetCachedTokenRecord.List.Current.OAuthTokenCache.Token
  |
  v
[Assign]  -- Build header
  NewHeader.Name  = "Authorization"
  NewHeader.Value = "Bearer " + BearerToken
  |
  v
[ListAppend]  Request.Headers += NewHeader
  |
  v
[Assign]  CustomizedRequest = Request
  |
  v
End
```

## Common mistakes

- **Hardcoding API keys or client secrets directly in the REST method URL or headers fields in Service Studio** — undefined Fix: 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.
- **Using Authentication = None on an exposed REST API in a production environment** — undefined Fix: 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.
- **Fetching a new OAuth2 token on every API call instead of caching it** — undefined Fix: 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.
- **Raising a User Exception instead of a Security Exception for authentication failures in OnAuthentication** — undefined Fix: 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.

## 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.

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-api-authentication
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-api-authentication
