All Request Headers Available in AWS Amplify Hosting (Geo / ASN / Device)
TL;DR
The headers that reach the request handler in AWS Amplify Hosting SSR include a lot of user information added by CloudFront.
Without installing MaxMind, geoip-lite or the like, you can get the country, region, city, postal code, latitude/longitude, ASN, and device type as they are.
All the headers that actually arrive
I dumped all of request.headers on a production deployment and confirmed the following.
Geo (location) headers
| Header | Example value | Meaning |
|---|---|---|
cloudfront-viewer-country |
JP |
ISO 3166-1 alpha-2 country code |
cloudfront-viewer-country-name |
Japan |
Country name (English) |
cloudfront-viewer-country-region |
13 |
ISO 3166-2 subdivision code (Tokyo) |
cloudfront-viewer-country-region-name |
Tokyo |
Region / state name |
cloudfront-viewer-city |
Matsubara |
City name |
cloudfront-viewer-postal-code |
156-0041 |
Postal code |
cloudfront-viewer-latitude |
35.xxxxxx |
Latitude |
cloudfront-viewer-longitude |
139.xxxxxx |
Longitude |
cloudfront-viewer-time-zone |
Asia/Tokyo |
Time zone |
It goes as far as the "postal code" and "latitude/longitude". Handle with care in terms of privacy.
The city name was not the city I was actually accessing from; it was about 3 km away.
Network headers
| Header | Example value | Meaning |
|---|---|---|
cloudfront-viewer-asn |
25XX |
AS number |
cloudfront-viewer-address |
203.0.xxx.xxx:53xxx |
Client IP and port |
cloudfront-viewer-tls |
TLSv1.3:TLS_AES_128_GCM_SHA256:connectionReused |
TLS version and cipher suite |
cloudfront-viewer-http-version |
3.0 |
HTTP version |
cloudfront-forwarded-proto |
https |
Client-side protocol |
x-forwarded-for |
203.0.xxx.xxx, 64.252.xxx.xxx |
List of IPs along the proxy path |
x-forwarded-host |
example.com |
Client-side Host |
x-forwarded-port |
443 |
Client-side port |
x-forwarded-proto |
https |
Client-side protocol |
via |
3.0 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront) |
Via-CloudFront information |
Some of the information is anonymized.
Getting even the ASN is nice. With it you can classify requests as "came from a specific ISP" or "came from an AWS data center".
Device type
| Header | Example value |
|---|---|
cloudfront-is-desktop-viewer |
true |
cloudfront-is-mobile-viewer |
false |
cloudfront-is-tablet-viewer |
false |
cloudfront-is-smarttv-viewer |
false |
cloudfront-is-ios-viewer |
false |
cloudfront-is-android-viewer |
false |
You can tell desktop / mobile / tablet / TV without parsing the UA. CloudFront looks at the UA and makes the judgment for you.
AWS internal trace headers
| Header | Purpose |
|---|---|
x-amzn-trace-id |
Trace ID for X-Ray and similar |
x-amz-cf-id |
CloudFront request ID (needed when contacting support) |
Original browser headers (not overwritten!)
| Header | Example value |
|---|---|
user-agent |
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ... Chrome/146.0.0.0 ... |
accept-language |
ja,en-US;q=0.9,en;q=0.8 |
host |
example.com |
In the past (around 2022) there was a GitHub issue (#2161) saying "Amplify SSR replaces user-agent with Amazon CloudFront", but now the original browser UA is kept. You can extract the details with ua-parser-js or the like.
How to read them in SvelteKit SSR
You can get them in the normal way from the headers of the request: Request that is passed in the arguments of load functions and actions handlers.
Example: load function
// src/routes/+page.server.ts
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ request }) => {
const headers = request.headers;
const country = headers.get('cloudfront-viewer-country-name'); // 'Japan'
const region = headers.get('cloudfront-viewer-country-region-name'); // 'Tokyo'
const asn = headers.get('cloudfront-viewer-asn'); // '25XX'
return {
location: { country, region, asn }
};
};
Example: action handler
// src/routes/+page.server.ts
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request }) => {
const country = request.headers.get('cloudfront-viewer-country');
// ...
}
};
Example: API endpoint (+server.ts)
// src/routes/api/some/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ request }) => {
const country = request.headers.get('cloudfront-viewer-country');
return new Response(JSON.stringify({ country }));
};
Wrap it up as a utility
In a real project, putting the extraction logic into a utility function makes it easier to handle.
// src/lib/server/geo.ts
export type DeviceType = 'desktop' | 'mobile' | 'tablet' | 'tv' | undefined;
export interface GeoInfo {
country?: string;
countryCode?: string;
region?: string;
asn?: number;
deviceType?: DeviceType;
}
export function extractGeoInfo(headers: Headers): GeoInfo {
const asnRaw = headers.get('cloudfront-viewer-asn');
const asn = asnRaw ? parseInt(asnRaw, 10) : undefined;
let deviceType: DeviceType;
if (headers.get('cloudfront-is-tablet-viewer') === 'true') {
deviceType = 'tablet';
} else if (headers.get('cloudfront-is-mobile-viewer') === 'true') {
deviceType = 'mobile';
} else if (headers.get('cloudfront-is-smarttv-viewer') === 'true') {
deviceType = 'tv';
} else if (headers.get('cloudfront-is-desktop-viewer') === 'true') {
deviceType = 'desktop';
}
return {
country: headers.get('cloudfront-viewer-country-name') || undefined,
countryCode: headers.get('cloudfront-viewer-country') || undefined,
region: headers.get('cloudfront-viewer-country-region-name') || undefined,
asn: asn !== undefined && Number.isFinite(asn) ? asn : undefined,
deviceType
};
}
The calling side.
import { extractGeoInfo } from '$lib/server/geo';
export const load: PageServerLoad = async ({ request }) => {
const geo = extractGeoInfo(request.headers);
console.log(geo);
// { country: 'Japan', countryCode: 'JP', region: 'Tokyo', asn: 25XX, deviceType: 'desktop' }
};
References
We look forward to discussing your development needs.