Introduction
Phishing is among the most common causes of data breaches. Attackers often trick users into inputing their credentials into websites that look legitimate to the human-eye but are actually setup by the attacker to steal credentials.
While MFA is an effective tool to reduce this kind of attacks, it is unfortunately insufficient. Multiple campaigns in the past have shown how attackers can get around MFA through various techniques.
WebAuthn/Passkeys provide strong protection against phishing attacks. In fact, all compliant browsers enforce a number of security checks that render phishing unfeasible in the majority of scenarioes.
Enforcing access to apps through Passkeys is not always possible as the company might not have access to the source code or might not have the resources to dedicate to the project. Gate is an effective way to easily enforce complex authentication and authorization policies out-of-the-box on web applications without any code changes.
Read on to see an example on how to add fine-grained access control and Passkeys auth to an arbitrary application in 10 minutes or less.
WebAuthn and phishing
WebAuthn/Passkeys are public-private keypairs where only the public key is transmitted to the server, making credential stealing unfeasible unless an attacker compromises a user machine.
Further, the WebAuthn spec and implementations in the browsers have been designed to be phishing-resistant and several checks are in place for an authentication ceremony to happen.
First of all, WebAuthn doesn’t work without TLS. Furthermore, browsers enforce origin checks and most will prevent access to the platform authenticator unless the window is in focus or, in the case of Safari, the user triggers an action.
In other words, an attacker trying to compromise user credentials will need to either find a cross-site scripting (XSS) bug in the target website or a vulnerability in the browser, both of which are very high barriers to overcome. This is the only way in which they can bypass the WebAuthn checks.
In either scenario, a successful attack still won’t give the attacker access to the private key itself, but only to a session token/cookie which will expire once the browsing session is over.
Most importantly, due to the origin checks, most of the recent attacks involving domain squatting and phishing would fall flat because the reverse proxy wouldn’t be able to initiate the WebAuthn authentication process.
In comparison to all other authentication methods — where an attacker only has to register a seemingly related domain and have a similar looking webpage to fool the victim through phishing — it is clear that WebAuthn is a vastly superior authentication method.
The internal app: YouTrack
For this example we are going to assume that we have a self-hosted deployment of YouTrack, the popular issue tracker software. For simplicity, we’ll use docker compose for the deployment but Gate supports several deployment topologies.
version: '3.3'
services:
youtrack:
image: jetbrains/youtrack@sha256:<hash>
container_name: 'youtrack'
volumes:
- /mnt/disks/youtrack-data/youtrack/data:/opt/youtrack/data
- /mnt/disks/youtrack-data/youtrack/conf:/opt/youtrack/conf
- /mnt/disks/youtrack-data/youtrack/logs:/opt/youtrack/logs
- /mnt/disks/youtrack-data/youtrack/backups:/opt/youtrack/backups
expose:
- '8080'
Deploying Gate
We are going to deploy Gate in front of the application such that Gate can intercept unauthenticated requests and prompt the user to login. Visually, it looks as follows:
What the Authentication Proxy plugin does is to intercept HTTP requests and if no valid authentication token is found, Gate injects a login page in front of the application. The user logs in with one of the allowed login methods and is then redirected to the original app (in this case YouTrack).
Once the user has authenticated the first time, future requests are passed to YouTrack transparently.
Here’s what the docker compose looks like:
version: '3.3'
services:
youtrack:
image: jetbrains/youtrack@<hash>
container_name: 'youtrack'
volumes:
- /mnt/disks/youtrack-data/youtrack/data:/opt/youtrack/data
- /mnt/disks/youtrack-data/youtrack/conf:/opt/youtrack/conf
- /mnt/disks/youtrack-data/youtrack/logs:/opt/youtrack/logs
- /mnt/disks/youtrack-data/youtrack/backups:/opt/youtrack/backups
expose:
- '8080'
gate:
container_name: gate
image: slashid/gate:latest
command: ['-env']
hostname: gate
restart: unless-stopped
environment:
- GATE_PORT=80
- GATE_DEFAULT_TARGET=http://youtrack:8080
- GATE_LOG_FORMAT=text
- GATE_LOG_LEVEL=info
- GATE_HEADERS_FORWARDED_SET_OUTBOUND_X_FORWARDED=true
- GATE_HEADERS_FORWARDED_PRESERVE_INBOUND_X_FORWARDED=true
- GATE_PLUGINS_1_TYPE=authentication-proxy
- GATE_PLUGINS_1_ENABLED=true
- GATE_PLUGINS_1_PARAMETERS_LOGIN_PAGE_HEADING=Welcome to YouTrack
- GATE_PLUGINS_1_PARAMETERS_STORE_LAST_HANDLE=true
env_file:
- .secret.env
ports:
- '80:80'
depends_on:
- youtrack
Customizing the login page
The authentication proxy plugin supports a number of customization, as shown in the documentation.
In particular, it is possible to specify the valid authentication methods supported:
login_page_factors:
- method: 'webauthn'
options:
<Factor option>: '<Factor option value>'
- method: 'oidc'
options:
<Another factor option>: '<Another factor option value>'
Creating complex authorization policies
The authentication proxy plugin also allows to out-of-the-box restrict access to users belonging to specific groups.
For example, we could restrict access to users belonging to the developers
group.
required_groups: ['developers']
But sometimes it might be necessary to enforce more complex policies. For instance, we might want to restrict access to users that belong to certain google groups. To do so, we can combine the OPA plugin with the Authentication Proxy plugin. Let’s see how:
...
- id: authz_admin_oidc
type: opa
enabled: true
parameters:
<<: *slashid_config
policy_decision_path: /authz/allow
policy: |
package authz
import future.keywords.if
default allow := false
allow if claims.oidc_tokens[0].oidc_groups[_] == "developers@example.com"
parseCookies(cookies) = cookie {
parsed = split(cookies, ";")
cookie = { k: v |
raw = parsed[_]
pair = split(raw, "=")
k = trim(pair[0], " ")
v = trim(substring(raw, count(sprintf("%s=", [pair[0]])), count(raw)), " ")
}
}
cookies := c if {
v := input.request.http.headers.Cookie[0]
c := parseCookies(v)
}
claims := payload if {
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t if {
t := cookies["SID-AuthnProxy-Token"]
}
- id: auth_proxy
type: authentication-proxy
enabled: true
parameters:
<<: *slashid_config
login_page_heading: "Hello!"
jwt_expected_issuer: https://api.slashid.com
jwks_url: https://api.slashid.com/.well-known/jwks.json
login_page_factors:
- method: "oidc"
options:
client_id: "<google client id>"
provider: "google"
urls:
- pattern: "*"
target: http://youtrack:8000
plugins:
auth_proxy:
enabled: true
authz_admin_oidc:
enabled: true
...
Let’s break it down, the authentication proxy configuration specifies oidc
as the only valid authentication method. You can read more here on how to configure SlashID to retrieve Google groups as part of the authentication process with OIDC.
Note: you could do the same with AzureAD. Read here to learn more.
The authz_admin_oidc
instance of the OPA plugin enforces the check on the presence of Google groups as follows:
- Parse the cookies in the headers
- Extract the bearer token from the SID-AuthnProxy-Token header
- Check if the oidc_groups array in the token contains the developers@example.com group
We could, of course, further restrict the policy to, for instance, specific IP addresses or user agents. The Gate OPA plugin provides a rich input
object to the policy with significant information on the incoming request.
Seeing it in action
Conclusion
In this post, we’ve shown how we can easily add Passkeys and fine-grained authorization policies to an arbitrary web application in less than 10 minutes without modifying its source code.
We’d love to hear any feedback you may have! Try out Gate with a free account. If you’d like to use the PII detection plugin, please contact us at contact@slashid.dev.