Last Night Otoroshi Saved My Life β #1: one app, three exposures
First article in the Otoroshi + Clever Cloud series. We start with the foundational use case: a single backend application, exposed three different ways β without touching the code.
Aux Alentours par MAIF β context
Aux Alentours par MAIF is an application that lets users consult natural and technological risks from a given address, and get tailored prevention advice and solutions. The infrastructure consists of two applications deployed on Clever Cloud:
- The web frontend β
auxalentours.maif.fr, the user interface - The API β the backend that powers the frontend and exposes several types of data
Otoroshi is deployed on a Clever Cloud JVM scaler β a custom installation, because when the platform was first set up, the Otoroshi add-on did not exist yet. It sits in front of the entire platform: both the web frontend and the API.
The applications are deployed on internal domains (*.innovation.maif) and are never directly accessible. To enforce this, Otoroshi uses the exchange protocol: a mechanism that allows backends to verify that incoming requests have actually passed through Otoroshi, and reject them otherwise. Worth noting: Clever Cloud now offers Request Flow, a native feature that serves the same purpose without having to implement the challenge on the application side.
graph LR
Users["π€ Users"]
Partners["π€ Partners"]
Oto["βοΈ Otoroshi
Clever Cloud JVM scaler"]
Site["π Web frontend
Clever Cloud"]
API["π₯οΈ API
Clever Cloud"]
Users --> Oto
Partners -->|"dedicated API key"| Oto
Oto -->|"exchange protocol"| Site
Oto -->|"exchange protocol"| API
This article focuses on the API. It exposes three very different profiles: a secured REST API, public documentation, and a map tile service. Three reasons not to expose them the same way.
Three routes, one application
In Otoroshi, each route defines a frontend (incoming domain + path) and a backend (target). There is no need to create a separate backend object: the target is declared directly in the route. All three routes point to the same Clever Cloud application (app-xxxxxxxx.innovation.maif), but with different configurations.
graph TD
R1["API route
api.auxalentours.innovation.maif
π API key required"]
R2["Doc route
api.auxalentours.innovation.maif/doc
π Public access"]
R3["Tiles route
tiles.auxalentours.innovation.maif
π Public access"]
Backend["π₯οΈ app-xxxxxxxx.innovation.maif
Aux Alentours par MAIF API"]
R1 --> Backend
R2 --> Backend
R3 --> Backend
Plugins common to all routes
Four plugins are present on each route:
OverrideHostβ replaces theHostheader of the outgoing request with the backend hostname. Required for the application to receive the correct host.ForceHttpsTrafficβ automatically redirects HTTP requests to HTTPS.DisableHttp10β rejects HTTP/1.0 requests, which are obsolete and ill-suited to modern traffic volumes.Robotsβ blocks search engine indexing (weβll cover this in detail in article 2).
Route 1 β API secured by API keys
"frontend": {
"domains": ["api.auxalentours.innovation.maif"],
"strip_path": true
},
"backend": {
"targets": [{ "hostname": "app-xxxxxxxx.innovation.maif" }],
"root": "/api/"
}
Requests come in on api.auxalentours.innovation.maif. With strip_path: true and root: /api/, Otoroshi forwards to /api/<path> on the backend. The route does not filter by path β it covers the entire domain.
The ApikeyCalls plugin is added with mandatory: true β any request without a valid key receives a 401.
How API keys work in Otoroshi
An API key consists of a client ID and a client secret. Otoroshi supports several ways to transmit them; here we use the bearer token, via the standard HTTP header:
Authorization: Bearer otoapk_<api-key-id>_<hash>
The token is a dedicated token generated by Otoroshi, used directly in requests. It is validated on the Otoroshi side by the oto_bearer extractor of the ApikeyCalls plugin.
Quotas (requests per day, per month) can be configured, as well as IP or domain restrictions. The Aux Alentours par MAIF frontend has its own key to consume the API β as do partners, each with a dedicated key and adjusted limits. If a key is compromised, it can be revoked without impacting other clients.
Route 2 β Public documentation
"frontend": {
"domains": ["api.auxalentours.innovation.maif/doc"],
"strip_path": false
},
"backend": {
"targets": [{ "hostname": "app-xxxxxxxx.innovation.maif" }],
"root": "/"
}
The documentation is exposed on the same domain as the API (api.auxalentours.innovation.maif), but on the /doc path. With strip_path: false, the path is forwarded as-is to the backend.
No authentication plugin is added: the documentation is public. Otoroshi routes requests to /doc unconditionally, even though the main API route requires a key.
Route 3 β Tile API
"frontend": {
"domains": ["tiles.auxalentours.innovation.maif"],
"strip_path": true
},
"backend": {
"targets": [{ "hostname": "app-xxxxxxxx.innovation.maif" }],
"root": "/tiles/"
}
Map tiles are exposed on a dedicated subdomain (tiles.auxalentours.innovation.maif). With strip_path: true and root: /tiles/, Otoroshi forwards to /tiles/<z>/<x>/<y>.png on the backend.
Tiles are public β they are consumed directly by the browser, and a single map view can trigger dozens of parallel requests. Exposing tiles on a separate subdomain brings immediate architectural clarity and makes it easier to apply differentiated traffic policies β including caching, which weβll cover in article 3.
Overview
| Route | Frontend | Auth | Notes |
|---|---|---|---|
| API | api.auxalentours.innovation.maif |
API key | ApikeyCalls mandatory |
| Documentation | api.auxalentours.innovation.maif/doc |
None | Public access |
| Tiles | tiles.auxalentours.innovation.maif |
None | β |
Key takeaways
Otoroshi makes it possible to decouple an applicationβs exposure policy from its implementation. A single application can be accessed in radically different ways depending on the context, with no code changes and no additional deployments.
The granularity operates at two levels: the domain (dedicated subdomain for tiles) and the path (same domain, different path for documentation). In both cases, each route has its own plugin pipeline, completely independent from the others.
In the next article, we stay with Aux Alentours par MAIF for two concrete HTTP use cases (CORS and robots.txt), before moving on to independent scenarios (HTTP redirects, iframe).