ATO prevention and account abuse prevention have emerged as essential tools in ensuring that your users and their accounts are safe, and Castle provides a nuanced solution in these areas. If you are using your own authentication solution - perhaps one you have built in-house - to authenticate users into your applications, then deploying Castle is very straightforward: you control every step of the authentication process, so it's easy to call out to Castle at the appropriate points to ensure that the request is not coming from a bot, and that the request is not part of an ATO attempt. Your application and Castle cooperate very effectively in this context to ensure users' accounts are safe.
But what if you're using an external identity provider, like Okta, Auth0, or Amazon Cognito for authentication? Or you allow users to log in using a global consumer identity provider such as Google or Facebook? Can you still apply Castle's leading security controls in these situations?
The answer is "yes", but like many topics in software and security, there is not a one-size-fits-all design pattern; there are trade-offs. But, there are strategies you can adopt to add Castle's layer of bot detection and ATO prevention to your OIDC authentication flow, getting the best of both worlds.
First, let's look at a standard OIDC authorization code grant flow.
The challenge here is that when your application redirects the authentication request to the OIDC provider, it surrenders the whole authentication experience to the OIDC provider. This of course is one of the big value-adds of OIDC: being able to outsource your user authentication so you don't have to worry about it. However, what if you want to augment (or replace) the OIDC provider's built-in ATO prevention strategies with a dedicated solution like Castle?
Approach #1: Proxy the OIDC provider
The most comprehensive solution is to proxy the OIDC provider.
If your OIDC provider supports custom domains, then you can direct all traffic to your own domain and process the user's authentication attempt before passing the username and password to the OIDC provider. For example, if your off-the-shelf authentication endpoint with your OIDC provider is mycompany.oidccloud.com/authenticate, then you would set up login.myapp.com/authenticate (for example) to handle the POSTs to the /authenticate endpoint.
Note: the OIDC provider must be able to somehow prevent traffic from reaching the mycompany.oidccloud.com/authenticate endpoint directly without going through your proxy.
Proxying the OIDC provider's /authenticate endpoint allows you to send the context of the POST request to Castle ($login.attempted) before sending the username and password to the OIDC provider, and, after a successful authentication, it allows you to send the context of the request again to Castle ($login.succeeded) for evaluation relative to ATO. Importantly, this approach allows you to keep the result of the authentication attempt hidden from the potential attacker: if the username and password are correct, but Castle responds with a DENY verdict, then you respond to the request with a simple 401.
Proxying the OIDC provider is the most heavy-handed approach, but it's also the approach that will most likely ensure your users' account safety.
Approach #2: "Provisional" authentication
Another approach is to treat the authentication from the OIDC provider as a "provisional" authentication: the user has extremely limited (or no) authorization to do anything inside the application until Castle has determined that the authentication is not part of an ATO attempt. In other words:
- you do not create a local application session for the user until Castle responds with an ALLOW verdict
- if Castle responds with a DENY verdict, you immediately kill the user's session with the OIDC provider (and initiate a password reset flow for the user)
- as an extra measure of security, if you are using your OIDC provider to generate access tokens for API access, you will want to review and test the timing around killing the external OIDC session vs. requesting an access token from the OIDC provider.
This approach of course bleeds into "authorization" rather than "authentication", and it relies on a level of discipline in your application code. But this approach can also be a way to leave your OIDC integration mostly "as-is" without having to do a proxy. I should note that one significant downside to this approach is that if an attacker has a set of valid credentials, then they will be able to confirm that the credentials are correct, which is not ideal. Trade-offs.
But, it's also important to note that this approach can work for the global consumer OIDC providers (Google, Facebook, etc.) and other federation providers as well. You're not going to be able to proxy Google's consumer /authn endpoint, but this "provisional authentication" approach can give you and your users an extra layer of security.
Challenge / MFA
Up to this point, I've glossed over the role of MFA in the authentication flow for an external OIDC provider. All modern SaaS OIDC providers support MFA in some form, and have varying degrees of sophistication in the policies that govern when MFA is triggered.
Our recommendation is to rely on Castle to indicate when a user should be challenged for a 2nd factor, rather than the OIDC provider’s policies, because Castle's sophisticated risk engine will give you the best guidance on whether a challenge is necessary. (You should still use the OIDC provider’s API for the mechanics of challenging the user.). This means that when you use Castle’s policies, you’re ensuring that bad actors are challenged, but also ensuring that regular users do not encounter unnecessary friction in their login experience.
I hope this overview has given you some ideas on how best to deploy a bot detection and ATO prevention solution like Castle alongside your OIDC provider. The right approach for you will of course depend on the trade-offs among cost, security, LOE, and user experience.