Security for API endpoint accessed by frontend users and 3rd party systems

Jakub Jóźwicki
7 min readOct 3, 2024

--

To select a correct method we should start with a threat modeling.
At the beginning we focus on an interactive user, accessing API via WebUI frontend. This user would have a browser on a desktop or a mobile application — we can generalize that the component interacting with API would be some form of WebKit engine.

We assume an attacker trying to intercept the access credentials — basically username and password. The user would be commonly attacked via phishing and to achieve the credential stealing a fake login screen may be used (for example: man in the browser) or a keylogger (this may be dropped via HTML smuggling technique bypassing Internet security gateway). On Windows desktop it’s quite easy to write a keylogger with dynamic lookup of Win32 keyboard-related functions without the need for elevated (admin) privileges (see: https://jakub-jozwicki.medium.com/obfuscated-keylogger-in-c-b614465f5114). What’s more: default built-in OS anti-virus Windows Defender doesn’t prevent this kind of keylogger, also enterprise grade MS Defender for Endpoints by default reports mentioned code as telemetry, not as detection or alert. The credential stealing is a real threat and as mitigation to authenticate securely at WebUI more than 1 authentication factor is needed. The 2nd factor in MFA should be a so called “phishing resistant factor” — usually a hardware device generating one time password (OTP), most commonly a 6 or 8 digit number with quick rotation and expiration. We can assume OAuth 2.0 with authorization code flow for both desktop and mobile client, however mobile client must explicitly call external browser and not use embedded Web View, because the user in the mobile app can’t verify there identity of IDP (visible URL, HTTPS certificate, see: https://www.rfc-editor.org/rfc/rfc8252).

Having MFA in place we can still suffer from MITM attack (a proxy decrypting traffic and encrypting with own HTTPS certificate added to Trusted Root Certification Authorities) stealing authorization code or access token. The attacker can reply request for authorization code or access token from different IP. As a basic mitigation the unique parameters state and nonce can be used by IDP to block already seen values. IDP should be smart enough to treat this situation as a security incident especially when the 2 IPs sending the same requests are in 2 different geographical locations. IDP should support conditional access policies allowing access only from trusted locations/IP ranges. To mitigate the token stealing any form of Proof of Possession technique can be used (for example code challenge: https://datatracker.ietf.org/doc/html/rfc7636). To protect from the token stealing there is a refresh token and expiration time set on tokens. Expired token can’t be used (would be rejected by protected resource), so the attacker has got limited time window to use stolen data.

When the attacker in MITM mode decides not to reply the stolen token but to use it on his/her own, it can block or pass original HTTPS request. When the request is blocked and times out (or there is other connection error) the user agent should try to report the problem back to the web application, because it might be a security incident. In case the request is passed the OAuth 2.0 flow would fail on duplicate and this also should be treated by IDP as a security incident.

Mentioned token expiration is not enough to mitigate all attacks. When the access token is JWT there is jti field in it uniquely identifying the token. This JWT id can be used to detect and prevent duplicates. Any access to IDP should be authenticated (client_id + client_secret or HTTP Basic Authentication).
To sum up: for frontend access OAuth 2.0 authorization code flow can be used with recommended PoP extension, supported by modern security-oriented cloud-deployed IDP. The protocol can be passed by AWS Network and Application Load Balancer. ALB supports AWS WAF, so rules to allow traffic for selected API endpoints can be configured, also using content types, HTTP header matching (like expected User Agent), IP/regional matching + reputation checks, built-in protection against SQL injection and cross-site scripting. AWS Shield can be used to prevent (D)DoS attacks.

The system calling API endpoint is not a live person and we can’t use MFA. Why do we need an equivalent of MFA? Because basic access credentials by mistake can reside in a code repository and can be leaked. As a second factor we can use some form of password protected private key. Having ALB we don’t want to be forced to use OAuth 2.0 client credentials grant + mTLS (X509 authentication of the caller). Instead of private key in mTLS we can use private key as JWT client assertion (see: https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication,
https://datatracker.ietf.org/doc/html/rfc7523#section-2.2). We can manage all public keys in one place — at IDP, without the need to manage them also for TLS termination. What’s more — if we apply the Zero Trust principle (we don’t fully trust AWS) we can use AWS NLB instead of ALB and pass API traffic transparently as TCP/IP. In this case we still can use AWS Shield to prevent (D)Dos, but we need to implement own WAF (for example: Apache APISIX + Coraza). If our 3rd party doesn’t want to use Internet to call our API we may set up a AWS PrivateLink that requires explicit acceptance of connection request in AWS configuration and the traffic would travel via AWS backbone.

On the left side we can use NLB or ALB, but on the right side we have EKS with Istio. If we want to use default Istio ingress gateway a basic choice is NLB. We can have AWS Shield, routing to microservices on podman on EC2, but no AWS WAF. ALB can be connected to EKS ingress via ALB ingress controller deployed on EKS. To have Istio ingress with ALB, the Istio Gateway must be exposed as NodePort, not LoadBalancer.

ALB ingress controller can transparently support OIDC 1.0 (see: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.6/guide/ingress/annotations/#auth-idp-oidc) as well as AWS Cogito, however considering possibility of 0 day vulnerabilities an opportunistic attacker may probe whole cloud and harm also our tenant. My recommendation for AWS and especially Azure is not to use IDP from CSP. See: https://nvd.nist.gov/vuln/detail/CVE-2024-45037 “Authenticated Cognito users may gain unintended access to protected API resources or methods, leading to potential data disclosure, and modification issues”. By using a different IDP we potentially limit the attack surface, but it works on an opportunistic attacker not the one targeting us.

The conclusion is that for both interactive and non-interactive use cases we can use OIDC 1.0 which is built on top of OAuth 2.0.

Considering holistic API endpoint security we can have many layers:
1. AWS Shield to prevent (D)DoS and access from IP of known bad reputation.
2. AWS ALB with WAF to filter out malicious/probing traffic.
3. AWS ALB Ingress Controller with OIDC JWT verification (issuer, expiration, signature). Please note that OIDC on the callee side is visible in X-Amzn-Oidc* HTTP headers.
4. RequestAuthentication by Istio (also more fine grained AuthorizationPolicy can be used based on claims, scopes, audience). Verified JWT can be written to other header via jwtRules: outputPayloadToHeader: other-header-name.
5. If something more advanced is needed OPA can be used as Envoy’s external authorizer.
6. Validation in the API.

In Zero Trust approach a security mechanism at a single layer should assume that the protection on other layers failed. On the other hand deviation from simple configuration toward too complex setup is also a security risk. Too many validations come with a performance impact. Possible final solution might be: 1+2+3+5+6 or 1+2+4+6. Earlier rejection of unauthorized request is preferred, but still API itself shouldn’t be left without internal protection in case previous layers suffered from misconfiguration and passed all traffic without JWT token validation.

JWT should be validated:
- if issuer is expected
- if audience is expected (matches target API endpoint)
- if scope matches entity and CRUD action/HTTP verb (example: order_write is for order HTTP POST and order_read is for HTTP GET)
- if claims contain a role of the subject needed to perform the operation on the API endpoint
- if subject is expected (like user@mycompany.com)
- if token is not expired, not issued in future, valid against nbf
- if the token is not a duplicate (already consumed, jti was previously seen)
- if the signature is valid against assumed public key.

JWT’s signature and/or encryption should have an appropriate algorithm (see: https://datatracker.ietf.org/doc/html/rfc7518, recommended algs are RS256+ and ES256+). Currently we know that AES_128_CBC may be vulnerable and AES_256_GCM is more preferred. We may want JWT encryption in case claims are confidential and on user side we want to expose only unreadable blob. JWT expiration time for API calls doesn’t need to be longer than 60 seconds. The same JWT shouldn’t be accepted by 2 different API operations. When API chaining the JWT should not be duplicated and reused in the next call, it should be issued just for one call type.

The popular practice when exposing APIs to the frontend is to follow “backend for frontend” or orchestrator or facade pattern. There might be a dedicated endpoint interacting directly with WebUI and passing requests to underlaying APIs, fully covering them, so there is no direct access of the interactive user to APIs dedicated for system-to-system communication. This is often used when APIs expose advanced features, many parameters or output too many properties on returned entities. A facade may cover these complexities improving the security posture.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Jakub Jóźwicki
Jakub Jóźwicki

No responses yet

Write a response