-
Notifications
You must be signed in to change notification settings - Fork 10
API proxy
Amy Farrell edited this page Jan 22, 2025
·
5 revisions
Part of USAGov Architecture patterns
In order to use data from external web services "on the fly," we'll need a server to handle the connection to the external service.
This is a proposal for a service we have not yet implemented.
- Audience: general public (via client-side javascript)
- API requests may be user-specific and on-demand
- Purpose: Retrieve data from an API server for use in a web page or web application
- Authenticate securely with the API server
- Cache results (as appropriate for the external API)
- Rate-limit requests to the external API
- Support POST requests if the user's query should not be logged
- Can serve data that's updated in real time (dependent on the external service)
- Server-to-server API access protects API credentials
- UX must account for possibility of the API Proxy or the external service being down (graceful degradation, distinguish "outage" from "no data")
- More subject to overload or DDoS than a static site; consider caching responses where possible
- Might use a separate domain from main public site so it doesn't share the same WAF
C4Context
title API proxy
Boundary(internet, "Internet", "the web") {
Person(publicUser, "Public web user")
System("externalAPI", "External API Server")
Boundary(cloud_gov_boundary, "Cloud.gov", "") {
Boundary(usagov_boundary, "USAgov org boundary", "") {
Boundary(asg_restricted, "Restricted egress space", "") {
ContainerDb(cache_storage, "Cache", "might be in-memory")
Container(api_proxy, "API proxy", "real-time updates", "Accesses External API, returns result", "")
ContainerDb(credentials_service, "Credentials service", "Server's API creds")
}
Boundary(asg_public_egress, "Public egress space", "") {
Container(egress_proxy, "Egress proxy", "", "")
}
}
}
}
Rel(publicUser, api_proxy, "HTTPS", "HEAD/GET/POST/(maybe more)")
Rel(api_proxy, egress_proxy, "HTTPS", "HEAD/GET/*")
Rel(api_proxy, cache_storage, "TLS", "read/write")
Rel(api_proxy, credentials_service, "TLS", "read")
Rel(egress_proxy, externalAPI, "HTTPS", "HEAD/GET/POST/(maybe more)")
UpdateRelStyle(publicUser, api_proxy, $offsetX="-30", $offsetY="-120")
UpdateRelStyle(api_proxy, egress_proxy, $offsetX="-20", $offsetY="10")
UpdateRelStyle(egress_proxy, externalAPI, $offsetX="220", $offsetY="120")
UpdateRelStyle(api_proxy, cache_storage, $offsetX="-20", $offsetY="0")
UpdateRelStyle(api_proxy, credentials_service, $offsetX="0", $offsetY="30")
UpdateElementStyle(egress_proxy, $bgColor="yellow", $fontColor="black")
In these examples, the Egress proxy is not shown; it must be configured to allow access to the External APIs.
The API proxy applies credentials and passes the response back to the client:
sequenceDiagram
Public web user->>API proxy: Query? (POST)
API proxy->>External API Server: Query? (POST)
External API Server->>API proxy: Response (DATA)
API proxy->>Public web user: Response (DATA)
Happy-path example for Elected Officials tool using two external API services:
sequenceDiagram
Public web user->>API proxy: Reps for ADDRESS? (POST)
API proxy->>External API Server 1 (USPS): ADDRESS good? (POST)
External API Server 1 (USPS)->>API proxy: GOOD
API proxy->>External API Server 2 (Civic API): Reps for ADDRESS? (POST)
External API Server 2 (Civic API)->>API proxy: "DATA: Rep, rep, rep, rep ..."
API proxy->>Public web user: "DATA: Rep, rep, rep, rep ..."
The same flow, with caching added. Here, we get the same request twice in a brief period. It could be a legitimate user refreshing a page for some reason, or it could be part of a DDoS attack:
sequenceDiagram
Public web user->>API proxy: Reps for ADDRESS? (POST)
API proxy-->>Cache:hash(ADDRESS)?
Cache-->>API proxy: MISS
API proxy->>External API Server 1 (USPS): ADDRESS good? (POST)
External API Server 1 (USPS)->>API proxy: GOOD
API proxy->>External API Server 2 (Civic API): Reps for ADDRESS? (POST)
External API Server 2 (Civic API)->>API proxy: "DATA: Rep, rep, rep, rep ..."
API proxy-->>Cache: [hash(ADDRESS), "DATA: Rep, rep, rep, rep ..."]
API proxy->>Public web user: "DATA: Rep, rep, rep, rep ..."
Note over Public web user: time passes (less than n minutes)
Public web user->>API proxy: Reps for ADDRESS? (POST)
API proxy-->>Cache:hash(ADDRESS)?
Cache-->>API proxy: HIT: "DATA: Rep, rep, rep, rep ..."
API proxy->>Public web user: "DATA: Rep, rep, rep, rep ..."