こんにちは、広野です。
SPA (Single Page Application) では、技術的な制約上、フロントエンドのアプリ内でユーザのソース IP アドレスを取得することができません。そのため、一旦インターネットに通信を送って取得する必要があります。
ソース IP アドレスを返してくれるサービスは世の中にあるのですが、安心して使用するために自分で Amazon API Gateway だけでつくってみました。
セキュリティ要件から、アプリ側でソース IP アドレスをもとに何かしらの制御が必要になった際に活用できると思います。
やりたいこと
イメージは以下の図の通りです。

実現方法
- Amazon API Gateway Rest API を使用する。HTTP API ではダメ。
- Amazon API Gateway には主にログ取得を目的とした変数をデフォルトで持っており、その中にソース IP アドレスの変数がある。これをユーザへのレスポンスとして返してあげる。
- Amazon API Gateway で持つ変数を返すだけなので、他のサービスとの統合は不要。統合タイプは MOCK にする。
- Amazon API Gateway はまだ IPv6 に対応していないようで、(公式なドキュメントは見つけられませんでした)取得できる IP アドレスは IPv4 のみとなる。
気を付けるところは赤枠の2箇所。
統合リクエストの追記はこんな感じ。↓
統合レスポンスの追記はこんな感じ。↓
これ以外に CORS の設定がありますが、手作業だとハマる箇所なので、実装は最後に紹介する AWS CloudFormation テンプレートで行った方が確実です。CORS 対応のため、上記で紹介した GET メソッド以外に OPTIONS メソッドの定義も必要になります。テンプレートには含まれています。
SPA との結合例
ここでは React アプリからこの API を呼び出すときの実装例を紹介します。
アプリ側で、以下のコードを書きます。(関係ないコードは省略しています)
import React, { useEffect, useState } from 'react';
import axios from 'axios';
//state定義
const [sourceIp, setSourceIp] = useState();
//ソースIP取得関数
const getSourceIp = async () => {
const res = await axios.get("https://xxxxxxxxxxxxxxxxx.amazonaws.com/prod/GetSourceIp"); //API Gateway のエンドポイント
setSourceIp(res.data.sourceIp);
console.log(res.data); //データの確認用
};
//画面表示時にソースIP取得
useEffect(() => {
getSourceIp();
}, []);
//画面表示
return (
<p>sourceIp: {sourceIp}</p>
);
実際の画面では、以下のように表示されました。開発者コンソールでも、console.log で出力した情報が表示されています。

今回は画面表示させましたが、取得したソース IP アドレスをもとに条件分岐させて、画面内の処理を変えることができるようになると思います。
AWS CloudFormation テンプレート
とりあえず動く Amazon API Gateway のテンプレートを貼り付けておきます。
CORS 対応のためドメイン名をパラメータで入力しますが、ワイルドカード (*) にしたい方は適宜修正してご利用ください。
若干冗長な設定があるのは認識しているのですが、そこまで検証しきれず、リリースいたします。
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFormation template that creates a API Gateway and a relevant IAM roles for getting the source IP address.
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
SystemName:
Type: String
Description: System name.
Default: xxxxx
MaxLength: 10
MinLength: 1
DomainName:
Type: String
Description: Domain name for URL. xxxxx.xxx
Default: xxxxx.xxx
MaxLength: 40
MinLength: 5
AllowedPattern: "[^\\s@]+\\.[^\\s@]+"
SubDomainName:
Type: String
Description: Sub domain name for URL.
Default: example-xxxxx
MaxLength: 20
MinLength: 1
Resources:
# ------------------------------------------------------------#
# API Gateway Role (IAM)
# ------------------------------------------------------------#
ApiGatewayRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub example-ApiGatewayRole-${SystemName}
Description: This role allows API Gateways to execute.
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
- arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess
# ------------------------------------------------------------#
# API Gateway
# ------------------------------------------------------------#
RestApiGetSourceIp:
Type: AWS::ApiGateway::RestApi
Properties:
Name: !Sub example-RestApi-GetSourceIp-${SystemName}
Description: !Sub example REST API Gateway to get the user's source IP address for example-${SystemName}
EndpointConfiguration:
Types:
- REGIONAL
Tags:
- Key: Cost
Value: !Sub example-${SystemName}
RestApiDeploymentGetSourceIp:
Type: AWS::ApiGateway::Deployment
Properties:
RestApiId: !Ref RestApiGetSourceIp
DependsOn:
- RestApiMethodGetSourceIpGet
- RestApiMethodGetSourceIpOptions
RestApiStageGetSourceIp:
Type: AWS::ApiGateway::Stage
Properties:
StageName: prod
Description: production stage
RestApiId: !Ref RestApiGetSourceIp
DeploymentId: !Ref RestApiDeploymentGetSourceIp
TracingEnabled: false
Tags:
- Key: Cost
Value: !Sub example-${SystemName}
DependsOn: RestApiDeploymentGetSourceIp
RestApiResourceGetSourceIp:
Type: AWS::ApiGateway::Resource
Properties:
RestApiId: !Ref RestApiGetSourceIp
ParentId: !GetAtt RestApiGetSourceIp.RootResourceId
PathPart: GetSourceIp
DependsOn:
- RestApiGetSourceIp
RestApiMethodGetSourceIpGet:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApiGetSourceIp
ResourceId: !Ref RestApiResourceGetSourceIp
HttpMethod: GET
AuthorizationType: NONE
Integration:
Type: MOCK
Credentials: !GetAtt ApiGatewayRole.Arn
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
application/json: '{"sourceIp": "$context.identity.sourceIp"}'
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'"
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'"
PassthroughBehavior: WHEN_NO_MATCH
RequestTemplates:
application/json: '{"statusCode": 200}'
MethodResponses:
- StatusCode: 200
ResponseModels:
application/json: Empty
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Origin: true
DependsOn:
- RestApiResourceGetSourceIp
- ApiGatewayRole
RestApiMethodGetSourceIpOptions:
Type: AWS::ApiGateway::Method
Properties:
RestApiId: !Ref RestApiGetSourceIp
ResourceId: !Ref RestApiResourceGetSourceIp
HttpMethod: OPTIONS
AuthorizationType: NONE
Integration:
Type: MOCK
Credentials: !GetAtt ApiGatewayRole.Arn
IntegrationResponses:
- ResponseParameters:
method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,Cache-Control'"
method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS'"
method.response.header.Access-Control-Allow-Origin: !Sub "'https://${SubDomainName}.${DomainName}'"
ResponseTemplates:
application/json: ''
StatusCode: 200
PassthroughBehavior: WHEN_NO_MATCH
RequestTemplates:
application/json: '{"statusCode": 200}'
MethodResponses:
- ResponseModels:
application/json: Empty
ResponseParameters:
method.response.header.Access-Control-Allow-Headers: true
method.response.header.Access-Control-Allow-Methods: true
method.response.header.Access-Control-Allow-Origin: true
StatusCode: 200
DependsOn:
- RestApiResourceGetSourceIp
- ApiGatewayRole
まとめ
いかがでしたでしょうか?
なにげに世の中にこのような情報が少なかったので、ブログ記事にしてみました。
本記事が皆様のお役に立てれば幸いです。



