Envoy proxy
Envoy is a proxy that sits in front of Vascular Inbox. SDK traffic goes to Envoy first; Envoy forwards allowed requests to Vascular.
Why Envoy is needed
Web SDKs (React, React Native in the browser) cannot call Vascular Inbox on port 3000 directly. Browsers need a proxy that handles CORS and web-compatible routing. Point the SDK at Envoy on port 8081.
Your own authentication before Vascular — optional. If you want every SDK request validated by your login or identity system (not only Vascular API key and app key), the SDK can send a session-token on each call. Envoy sends that token to your authentication service first; only approved requests reach Vascular Inbox. You build and run that service — it is not part of Vascular. See Authentication.
Native SDKs (Flutter, Android, iOS) without a session-token may connect to Vascular Inbox on port 3000 directly and do not need Envoy.
| Scenario | SDKs | Envoy listener |
|---|---|---|
| Web SDKs | React, React Native (web) | 8081 — required |
| Your custom authentication | Web and/or native (with session-token) | 8081 (web) or 8082 (native) — required |
Request flow
Web SDKs (API key + app key only)
Web SDK → Envoy (:8081) → Vascular InboxYour authentication, then Vascular (session-token enabled)
Vascular SDK → Envoy → your authentication service → Vascular InboxPlaceholders
Copy one configuration below to envoy.yaml and replace placeholders before deploying (for example with envsubst):
| Placeholder | Description | Example |
|---|---|---|
${vascular_upstream_HOST} | Hostname of the Vascular service | vascular-inbox |
${vascular_upstream_PORT} | Port of the Vascular service | 3000 |
${ENVOY_API_DOMAIN} | Envoy API domain name (hostname clients call) | envoy.your-domain.com |
${CORS_ALLOW_ORIGIN} | Allowed browser frontend origin (web SDK configs only) | https://app.your-domain.com |
${auth_filter_HOST} | Hostname of your authentication service (session token configs only) | my-auth-service |
${auth_filter_PORT} | Port of your authentication service (session token configs only) | 9001 |
SDK endpoints
| Configuration | Example endpoint |
|---|---|
| Web SDK | https://envoy.your-domain.com:8081 |
| Web SDK + your authentication | https://envoy.your-domain.com:8081 |
| Native SDK + your authentication | https://envoy.your-domain.com:8082 |
| Native SDK (no Envoy) | https://vascular.your-domain.com:3000 |
Ports (8081, 8082, 3000) and hostnames are examples — configure them in your Envoy and Vascular Inbox deployment.
Do not point web SDKs at the Vascular Inbox hostname or port.
Configurations
1. Web SDKs — API key and app key only
Listener 8081 for React and React Native (web). No custom authentication service. Native SDKs use Vascular Inbox on port 3000 directly.
static_resources:
listeners:
- name: listener_8081
address:
socket_address:
address: 0.0.0.0
port_value: 8081
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["${ENVOY_API_DOMAIN}"]
cors:
allow_origin_string_match:
- exact: "${CORS_ALLOW_ORIGIN}"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: content-type,authorization,x-requested-with,x-grpc-web,x-user-agent,app-key,api-key,grpc-timeout
expose_headers: grpc-status,grpc-message,grpc-status-details-bin
max_age: "1728000"
routes:
- match:
prefix: /
route:
cluster: vascular_upstream
timeout: 30s
clusters:
- name: vascular_upstream
type: STRICT_DNS
dns_lookup_family: V4_ONLY
connect_timeout: 5s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: vascular_upstream
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ${vascular_upstream_HOST}
port_value: ${vascular_upstream_PORT}2. Web SDKs + your authentication — session token on web only
Listener 8081. Use when only web clients send a session-token and you validate it with your own service before Vascular Inbox.
Download web-sdks-auth-filter.yaml
static_resources:
listeners:
- name: listener_8081
address:
socket_address:
address: 0.0.0.0
port_value: 8081
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
http_filters:
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
if status ~= "401" and status ~= "403" then
return
end
local grpc_status = "16"
local grpc_message = "Unauthenticated"
if status == "403" then
grpc_status = "7"
grpc_message = "Permission%20denied"
end
response_handle:headers():replace(":status", "200")
response_handle:headers():replace("content-type", "application/grpc-web-text+proto")
response_handle:headers():replace("grpc-status", grpc_status)
response_handle:headers():replace("grpc-message", grpc_message)
response_handle:body(true):setBytes("")
end
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
http_service:
server_uri:
uri: http://${auth_filter_HOST}:${auth_filter_PORT}
cluster: auth_filter
timeout: 5s
authorization_request:
allowed_headers:
patterns:
- exact: session-token
- exact: app-key
- exact: api-key
- exact: content-type
- exact: x-grpc-web
- exact: grpc-timeout
failure_mode_allow: false
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["${ENVOY_API_DOMAIN}"]
cors:
allow_origin_string_match:
- exact: "${CORS_ALLOW_ORIGIN}"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: content-type,authorization,x-requested-with,x-grpc-web,x-user-agent,app-key,api-key,session-token,grpc-timeout
expose_headers: grpc-status,grpc-message,grpc-status-details-bin
max_age: "1728000"
routes:
- match:
prefix: /
route:
cluster: vascular_upstream
timeout: 30s
clusters:
- name: auth_filter
type: STRICT_DNS
connect_timeout: 5s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options: {}
load_assignment:
cluster_name: auth_filter
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ${auth_filter_HOST}
port_value: ${auth_filter_PORT}
- name: vascular_upstream
type: STRICT_DNS
dns_lookup_family: V4_ONLY
connect_timeout: 5s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: vascular_upstream
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ${vascular_upstream_HOST}
port_value: ${vascular_upstream_PORT}3. All SDKs + your authentication — session token on web and native
Listeners 8081 (web, with CORS) and 8082 (Flutter, Android, iOS). Use when every SDK sends a session-token validated by your service before Vascular Inbox.
Download all-sdks-auth-filter.yaml
static_resources:
listeners:
- name: listener_8081
address:
socket_address:
address: 0.0.0.0
port_value: 8081
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
codec_type: AUTO
http_filters:
- name: envoy.filters.http.lua
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
inline_code: |
function envoy_on_response(response_handle)
local status = response_handle:headers():get(":status")
if status ~= "401" and status ~= "403" then
return
end
local grpc_status = "16"
local grpc_message = "Unauthenticated"
if status == "403" then
grpc_status = "7"
grpc_message = "Permission%20denied"
end
response_handle:headers():replace(":status", "200")
response_handle:headers():replace("content-type", "application/grpc-web-text+proto")
response_handle:headers():replace("grpc-status", grpc_status)
response_handle:headers():replace("grpc-message", grpc_message)
response_handle:body(true):setBytes("")
end
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
http_service:
server_uri:
uri: http://${auth_filter_HOST}:${auth_filter_PORT}
cluster: auth_filter
timeout: 5s
authorization_request:
allowed_headers:
patterns:
- exact: session-token
- exact: app-key
- exact: api-key
- exact: content-type
- exact: x-grpc-web
- exact: grpc-timeout
failure_mode_allow: false
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: local_route
virtual_hosts:
- name: backend
domains: ["${ENVOY_API_DOMAIN}"]
cors:
allow_origin_string_match:
- exact: "${CORS_ALLOW_ORIGIN}"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: content-type,authorization,x-requested-with,x-grpc-web,x-user-agent,app-key,api-key,session-token,grpc-timeout
expose_headers: grpc-status,grpc-message,grpc-status-details-bin
max_age: "1728000"
routes:
- match:
prefix: /
route:
cluster: vascular_upstream
timeout: 30s
- name: listener_8082
address:
socket_address:
address: 0.0.0.0
port_value: 8082
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_grpc
codec_type: AUTO
http_filters:
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: V3
http_service:
server_uri:
uri: http://${auth_filter_HOST}:${auth_filter_PORT}
cluster: auth_filter
timeout: 5s
authorization_request:
allowed_headers:
patterns:
- exact: session-token
- exact: app-key
- exact: api-key
- exact: content-type
- exact: grpc-timeout
failure_mode_allow: false
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
route_config:
name: grpc_route
virtual_hosts:
- name: backend
domains: ["${ENVOY_API_DOMAIN}"]
routes:
- match:
prefix: /
route:
cluster: vascular_upstream
timeout: 30s
clusters:
- name: auth_filter
type: STRICT_DNS
connect_timeout: 5s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http_protocol_options: {}
load_assignment:
cluster_name: auth_filter
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ${auth_filter_HOST}
port_value: ${auth_filter_PORT}
- name: vascular_upstream
type: STRICT_DNS
dns_lookup_family: V4_ONLY
connect_timeout: 5s
lb_policy: ROUND_ROBIN
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
load_assignment:
cluster_name: vascular_upstream
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: ${vascular_upstream_HOST}
port_value: ${vascular_upstream_PORT}Related
- Authentication — API keys and optional
session-token - Deploying Vascular Inbox — Docker and Kubernetes examples