All Request Headers Available in AWS Amplify Hosting (Geo / ASN / Device)

2026-05-22 14:08 (4 months ago)
Header Buffet
Play a song themed on this article

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

Please rate this article (No signup or login required)
Current rating: 5.0 (1)
The author runs the application development company Cyberneura.
We look forward to discussing your development needs.

Categories

Archive