OAuth
Overview
The OAuth module enforces a browser-based OAuth flow in front of your HTTP endpoints to an identity provider like Google.
See the full list of OAuth providers that ngrok supports.
Any client accessing an OAuth-protected endpoint will be redirected to the chosen identity provider for authentication. When they are redirected back to your endpoint, ngrok checks an optional set of authorization constraints (e.g. email address). If authorized, the user is forwarded to your upstream application and ngrok sets a cookie to avoid repeating the authentication flow.
ngrok sets headers for your upstream service with information about the authenticated user.
To get started faster, ngrok maintains managed OAuth apps for some identity providers so you don't need to bring your own client id and client secret. These managed OAuth applications are shared among ngrok accounts and have some additional limitations.
Example Usage
Auth with Google
Require all users authenticate with Google before accessing your upstream service.
You can also choose other supported providers like
github
or microsoft
.
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok http 80 --oauth google
tunnels:
example:
proto: http
addr: 80
oauth:
provider: "google"
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http --oauth=google
import (
"context"
"net"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
return ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithOAuth("google"),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
oauth_provider: "google",
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
oauth_provider="google")
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.http_endpoint()
.oauth(OauthOptions::new("google"))
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
---
kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
oauth:
google:
optionsPassthrough: false
inactivityTimeout: 4h
maximumDuration: 24h
authCheckInterval: 1h
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
k8s.ngrok.com/modules: ngrok-module-set
spec:
ingressClassName: ngrok
rules:
- host: your-domain.ngrok.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Restrict by email and domain
Allow only users who authenticate via Google with an email of
kate.libby@gmail.com
or emails that end in acme.org
to access your upstream
service.
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok http 80 \
--oauth google \
--oauth-allow-email kate.libby@gmail.com \
--oauth-allow-domain acme.org
tunnels:
example:
proto: http
addr: 80
oauth:
provider: "google"
allow_domains: ["example.com", "acme.org"]
allow_emails: ["kate.libby@gmail.com"]
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http \
--oauth google \
--oauth-allow-email kate.libby@gmail.com \
--oauth-allow-domain acme.org
import (
"context"
"net"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
return ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithOAuth("google",
config.WithAllowOAuthEmail("kate.libby@gmail.com"),
config.WithAllowOAuthDomain("acme.org"),
),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
oauth_provider: "google",
oauth_allow_emails: "kate.libby@gmail.com",
oauth_allow_domains: "acme.org",
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
oauth_provider="google",
oauth_allow_emails="kate.libby@gmail.com",
oauth_allow_domains="acme.org")
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.http_endpoint()
.oauth(OauthOptions::new("google")
.allow_email("kate.libby@gmail.com")
.allow_domain("acme.org")
)
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
---
kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
oauth:
google:
emailAddresses: ["kate.libby@gmail.com"]
emailDomains: ["acme.org"]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
k8s.ngrok.com/modules: ngrok-module-set
spec:
ingressClassName: ngrok
rules:
- host: your-domain.ngrok.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Bring your own OAuth app
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok http 80 \
--oauth microsoft \
--oauth-client-id "{client id}" \
--oauth-client-secret "{client secret}"
tunnels:
example:
proto: http
addr: 80
oauth:
provider: "microsoft"
client_id: "{client id}"
client_secret: "{client secret}"
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http \
--oauth microsoft \
--oauth-client-id "{client id}" \
--oauth-client-secret "{client secret}"
import (
"context"
"net"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
return ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithOAuth("microsoft"),
config.WithOAuthClientID("{client id}"),
config.WithOAuthClientSecret("{client secret}"),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
oauth_provider: "microsoft",
oauth_client_id: "{client id}",
oauth_client_secret: "{client secret}",
});
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
oauth_provider="microsoft",
oauth_client_id="{client id}",
oauth_client_secret="{client secret}")
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.http_endpoint()
.oauth(OauthOptions::new("microsoft")
.client_id("{client id}")
.client_secret("{client secret}")
)
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
---
kind: Secret
apiVersion: v1
metadata:
name: ngrok-oauth-secret
type: Opaque
data:
CLIENT_SECRET: "<base64-encoded-client-secret>"
---
kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
oauth:
microsoft:
clientId: "{client id}"
clientSecret:
name: ngrok-oauth-secret
key: CLIENT_SECRET
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
k8s.ngrok.com/modules: ngrok-module-set
spec:
ingressClassName: ngrok
rules:
- host: your-domain.ngrok.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Customize Scopes
Users must authenticate with GitHub and grant your application the repo
and
user
scopes to access your upstream service.
- Agent CLI
- Agent Config
- SSH
- Go
- Javascript
- Python
- Rust
- Kubernetes Controller
ngrok http 80 \
--oauth github \
--oauth-client-id "{client id}" \
--oauth-client-secret "{client secret}" \
--oauth-scope "repo" \
--oauth-scope "user"
tunnels:
example:
proto: http
addr: 80
oauth:
provider: "github"
client_id: "{client id}"
client_secret: "{client secret}"
scopes: ["repo", "user"]
ssh -R 443:localhost:80 v2@connect.ngrok-agent.com http \
--oauth github \
--oauth-client-id "{client id}" \
--oauth-client-secret "{client secret}" \
--oauth-scope "repo" \
--oauth-scope "user"
import (
"context"
"net"
"golang.ngrok.com/ngrok"
"golang.ngrok.com/ngrok/config"
)
func ngrokListener(ctx context.Context) (net.Listener, error) {
return ngrok.Listen(ctx,
config.HTTPEndpoint(
config.WithOAuth("github"),
config.WithOAuthClientID("{client id}"),
config.WithOAuthClientSecret("{client secret}"),
config.WithOAuthScope("repo", "user"),
),
ngrok.WithAuthtokenFromEnv(),
)
}
Go Package Docs:
const ngrok = require("@ngrok/ngrok");
(async function () {
const listener = await ngrok.forward({
addr: 8080,
authtoken_from_env: true,
oauth_provider: "github",
oauth_client_id: "{client id}",
oauth_client_secret: "{client secret}"},
oauth_scopes: ["repo", "user"]);
console.log(`Ingress established at: ${listener.url()}`);
})();
Javascript SDK Docs:
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#oauth_client_id
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#oauth_client_secret
- https://ngrok.github.io/ngrok-javascript/interfaces/Config.html#oauth_scopes
- https://ngrok.github.io/ngrok-javascript/classes/HttpListenerBuilder.html#oauth
import ngrok
listener = ngrok.forward("localhost:8080", authtoken_from_env=True,
oauth_provider="github",
oauth_client_id="{client id}",
oauth_client_secret="{client secret}",
oauth_scopes=["repo", "user"])
print(f"Ingress established at: {listener.url()}");
Python SDK Docs:
use ngrok::prelude::*;
async fn listen_ngrok() -> anyhow::Result<impl Tunnel> {
let sess = ngrok::Session::builder()
.authtoken_from_env()
.connect()
.await?;
let tun = sess
.http_endpoint()
.oauth(OauthOptions::new("github")
.client_id("{client id}")
.client_secret("{client secret}")
.scope("repo")
.scope("user")
)
.listen()
.await?;
println!("Listening on URL: {:?}", tun.url());
Ok(tun)
}
Rust Crate Docs:
---
kind: Secret
apiVersion: v1
metadata:
name: ngrok-oauth-secret
type: Opaque
data:
CLIENT_SECRET: "<base64-encoded-client-secret>"
---
kind: NgrokModuleSet
apiVersion: ingress.k8s.ngrok.com/v1alpha1
metadata:
name: ngrok-module-set
modules:
oauth:
github:
clientId: "{client id}"
clientSecret:
name: ngrok-oauth-secret
key: CLIENT_SECRET
scopes: ["repo", "user"]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
k8s.ngrok.com/modules: ngrok-module-set
spec:
ingressClassName: ngrok
rules:
- host: your-domain.ngrok.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Behavior
Callback URL
When you create your own OAuth app, you must specify a 'Callback URL' or 'Redirect URL' to the OAuth provider. When using ngrok's OAuth module, that Callback URL is always:
https://idp.ngrok.com/oauth2/callback
Authentication
When an unauthenticated request is made to an OAuth-protected endpoint, it returns a redirect response that begins an authentication flow with the configured identity provider. The original URI path is saved so that users can be redirected to it if they successfully authenticate.
If the user fails to authenticate with the identity provider, ngrok will display an error describing the failure returned by the identity provider and prompt them to try logging in again.
If the user successfully authenticates with the identity provider, ngrok will take the following actions:
- Check any authorization constraints you've defined (like allowed emails or allowed email domains). If the user is not authorized, ngrok renders an error and prompts them to try logging in again.
- Sets a session cookie to avoid repeating the authentication flow again.
- Redirects the user to the original URI path they were attempting to access
before the authentication flow began. If no such URI path was captured, they
are redirected to
/
.
Continuous Authorization
When an authenticated user makes a request, ngrok will sometimes refresh a user's data from the identity provider (email, name, etc) and re-evaluate authorization constraints. This refresh is executed as a backchannel request to the identity provider; it is transparent to the user and they do not go through a reauthentication flow.
The following circumstances trigger refresh and authorization re-evaluation:
- On a periodic interval defined by the the Auth Check Interval parameter.
- If you update the OAuth configuration of the endpoint by restarting your agent with a new configuration.
- If you update the OAuth configuration of the endpoint by modifying your Edge.
If a previously authenticated user becomes unauthorized because their identity provider information changed or because the OAuth module configuration changed, they are presented an error and are prompted to try logging in again.
Managed Applications
Managed applications allow you to use ngrok's OAuth module without setting up your own OAuth apps with the identity providers. More practically, this means you can use the OAuth module without configuring a client id and client secret.
Managed applications are great for getting started but they have some limitations.
- They are only available for some identity providers
- You may not pass custom scopes when using a managed application.
- The upstream headers
ngrok-auth-oauth-access-token
andngrok-auth-oauth-refresh-token
are not sent to your application.
App Users
ngrok's App Users feature can be used to observe all of the authenticated user activity across your account in the ngrok dashboard or via API. Whenever a user authenticates or accesses an endpoint with a configured OAuth module, their App User record is created or updated.
You may also use App Users to remotely log a user out by revoking a session.
Cookies
This module sets two cookies in its operation. Cookies values are opaque to the upstream service and must not be modified.
Cookie | Description |
---|---|
session | Used to track an authenticated user. |
nonce | Used to secure the authentication flow. |
Reference
Configuration
Parameter | Default | Description |
---|---|---|
Provider | - | The identifier of the OAuth identity provider to use for authentication. Supported OAuth Providers |
Client ID | - | Your OAuth app's Client ID. Leave this empty if you want to use a managed application. |
Client Secret | - | Your OAuth app's Client Secret. Leave this empty if you want to use a managed application. |
Allowed Emails | - | If specified, only users whose emails match a value in this list will be allowed. |
Allowed Email Domains | - | If specified, only users whose emails match one of the given domains in this list will be allowed. Note: This is a perfect match, so if you're using subdomains, all of them must be listed. |
Scopes | A list of additional scopes to request when users authenticate with the identity provider. | |
Auth Check Interval | 0 | When a request is received after this interval has passed since the last auth check or the initial authentication, ngrok will re-validate its authorization constraints. As part of this process, ngrok refreshes data about the authenticated user from the identity provider. If zero, authorization is only ever checked during an authentication flow. |
Inactivity Timeout | 0 | If an authenticated client does not make a request to the endpoint within this duration, they are forced to reauthenticate. If 0, no inactivity timeout is enforced. |
Maximum Duration | 0 | An authenticated client session may never last longer than this duration. If 0, no maximum duration is enforced. |
Options Passthrough | false | Don't enforce authentication on OPTIONS requests. Useful if this endpoint needs to be accessible via CORS. |
Upstream Headers
This module adds headers to the HTTP request sent to your upstream service with
details about the OAuth user who has authenticated. These headers will not be
set for OPTIONS
requests if you enable options passthrough.
Header | Description |
---|---|
ngrok-auth-user-id | Provider-defined identifier of the authorized user. |
ngrok-auth-user-name | Full name of the authorized user. |
ngrok-auth-user-email | Authorized user's primary email address. |
ngrok-auth-oauth-access-token | The user's OAuth access token. Undefined when using a managed application. |
ngrok-auth-oauth-refresh-token | The user's OAuth refresh token. Undefined when using a managed application. |
Special Paths
Upstream applications behind endpoints with this module enabled do not receive
any requests to paths beginning with /auth/
. Your application may redirect
clients to the following paths to invoke different behaviors.
Path | Description |
---|---|
/auth/authn | Redirect users to this path to explicitly begin an authentication flow. After authentication, users will be redirected to / . If the IdP supports it, ngrok will attempt to instruct the IdP to force reauthentication which will force users to re-enter their credentials with the IdP even if they were already logged in. |
/auth/logout | Logs the user out by clearing their session cookie. Redirect users to this path to log them out. |
Events
When this module is enabled, it populates the following fields in the http_request_complete.v0 event:
Fields |
---|
oauth.app_client_id |
oauth.decision |
oauth.user.id |
oauth.user.name |
Errors
This documentation is incomplete. Please check back later, we appreciate your patience.
Edges
OAuth is an HTTPS Edge module which can be applied to Routes.
Pricing
MAU stands for "Monthly Active User". Monthly Active Users are the number of uniquely authenticated OAuth users who have accessed your endpoints within a month.
OAuth MAU usage is calculated account-wide. It is not calculated on a per-endpoint basis.
An authenticated user with the same ID from the identity provider is counted as a single MAU even if they connect to multiple endpoints on your account.
Plan | MAUs |
---|---|
Free | 5 |
Personal | 50 |
Pro | Unlimited, usage-based-pricing |
Enterprise | Unlimited, usage-based-pricing |
Supported Providers
ngrok currently supports the following OAuth providers (see the Integration Guides for more details):
Provider | Provider Identifier | Managed App Available | Integration Guide |
---|---|---|---|
Amazon | amazon | no | Documentation |
facebook | no | Documentation | |
GitHub | github | yes | Documentation |
GitLab | gitlab | yes | Documentation |
google | yes | Documentation | |
linkedin | yes | Documentation | |
Microsoft | microsoft | yes | Documentation |
Twitch | twitch | yes | Documentation |
Try it out
Consult the list of supported providers for step-by-step integration guides.