How to Improve Your OAuth Developer Experience
These days, developer experience (DX) is a term used frequently but is often misunderstood. The truth is that a poor developer setup can negatively affect the time to market of your digital services. Especially when implementing security solutions, DX can suffer when a suboptimal technical design is chosen, which, unfortunately, is not uncommon.
Typically companies secure their apps using the OAuth family of specifications. The challenging security work is then outsourced to a third-party identity and access management (IAM) system. You deploy a component called the authorization server, which both your UIs and APIs interact with, to outsource difficult areas of security. Safely implementing this also requires good technical guidance from your IAM provider.
At its heart, OAuth is a simple and elegant framework based on a separation of concerns. It can also be developer friendly when used correctly. When implementing security solutions, a company needs to put care and consideration into first identifying requirements, and then choosing designs and security components that meet them. When this is not done, it can sometimes seriously affect business deliverables.
In this article, I will make some recommendations for improving the OAuth developer experience and enabling developers to operate in a more business-aligned way. This will be viewed from the perspective of web and API developers. I will also highlight some design patterns that enable your company to run the best security everywhere, including on developer workstations.
Many companies choose to build single-page applications (SPAs) to enable a fast and interactive user experience. From both a DX and business viewpoint, this solution is very aligned. The developer uses a modern productive technology to build the UI, and all efforts focus on a great user experience. When building SPAs, it is common to produce an initial security solution with the following parts.
One problem with this app design is its security, since it uses tokens in the browser and is likely to store a refresh token in local storage. This does not follow OAuth best practices for browser-based apps and is likely to be raised as an issue during penetration testing by security stakeholders. Instead, it is recommended to provide an application-level cookie layer for the SPA.
In some companies, architects may throw the cookie-based requirement at web developers. Yet solving this with quality DX requires API thinking, and web developers are not always the best ones to design this requirement. The developer may first search online, and then implement an initial solution that uses a reverse proxy or API gateway and a plugin component that implements OpenID Connect (OIDC).
The developer will then often encounter several pain points. First, the OIDC plugin might only be designed for websites and doesn’t support forwarding JSON Web Tokens (JWTs) to APIs. Second, it may deal poorly with expiration, such as having no support for refreshing underlying access tokens. Finally, it is unlikely to be SPA- or Ajax-friendly and may issue abrupt 302 redirects to Ajax responses, which is not the correct behavior for an SPA.
Eventually, the developer will realize that a backend for frontend (BFF) is needed to correctly deal with the above concerns in an SPA-friendly manner. The developer must then make a cookie-issuing technology choice. Often this choice matches the company’s websites, which could be coded in a language such as C#. This may result in a fat backend on a developer workstation.
The SPA now implements its security correctly, but this change has affected the DX. To get static web content for the React app, and for cookies to work, the React developer must now deal with many backend security concerns for all future development. In some cases, it may take considerable time for the developer to get the expiration, error handling and usability working reliably.
Yet it is possible to use a modern form of the backend-for-frontend pattern to optimize DX. This strategy involves separating the BFF concerns from static content delivery. The BFF is then deployed to an API domain, which must share the same parent domain as the SPA’s web origin so that cookies remain first party. The React developer then only has to write React code for all future development.
This is called the token handler pattern, in which the OAuth agent interacts with the authorization server, and then issues cookies on behalf of the SPA. The OAuth proxy ensures that API requests are managed in an SPA-friendly manner, translates cookies to tokens and also applies generic security checks. To integrate the token handler components, you should only need to configure and deploy tested implementations of the OAuth agent and OAuth proxy.
This design pattern separates web and API concerns to offer the best choices. It is still possible to deploy the OAuth agent and OAuth proxy to the web domain if that suits your web architecture. Yet a multidomain setup is likely to provide the best possible SPA developer experience.
Zero Trust APIs
DX also needs to be enabled for backend developers and testers, which can require insight when security is used. Consider the following mobile and API end-to-end flow, where multifactor authentication (MFA) is used, including time-based one-time passwords (TOTP) received from the Google Authenticator app on a mobile device.
The phantom token pattern is also used to keep tokens delivered to the mobile app confidential. The APIs adopt a zero trust approach, wherein JWT access tokens are forwarded between APIs, and each API digitally verifies the JWT on every request.
All of this is fine from a security viewpoint, but consider how it affects the developer of each microservice. By default, to get a user-level access token for testing, the developer must run a code flow, and then switch to the mobile app and enter the second factor. This is not a productive setup.
Solving this problem first requires understanding the basic OAuth flow for a secured API. The JWT access token provides a contract to the API. First, its digital signature is verified using an asymmetric public key. Next, its claims are used for authorization. A JWT library is also used to cache the token-signing public keys to ensure good performance.
Integration tests for the Order API will typically mock the call to the upstream Customer API. You can bypass difficult OAuth-related infrastructure using the same approach, as long as you provide the same JWT access token contract. The Order API does not need to test that authentication works. Instead, it can simply ask a utility HTTP endpoint for a token for a particular user. This endpoint will generate its own key pair, issue JWTs with the private key and make the public key available to the API.
The security code in the API is unchanged and behaves identically in deployed environments where the API is connected to a real authorization server. Yet the local setup design provides a good DX for developers of secured APIs and also enables them to develop securely. Mocking the authorization server’s JSON Web Key Set (JWKS) endpoint can be done either via a utility API or an HTTP tool such as WireMock.
Backend Infrastructure for Best Developer Experience
The API developer story does not end with writing and testing API code. There are a number of other components that developers will use and extend. The following components are particularly relevant to enabling a quality end-to-end DX throughout OAuth development.
Both the API gateway and the authorization server are critical parts of your architecture and need good extensibility to be managed via plugins and scripting. When implementing this type of task, backend developers will run a more complex setup locally with multiple components. There can be significant productivity benefits if developers can deploy them all locally. Doing so enables the end-to-end reliability to be verified as early as possible in your deployment pipeline, after which the same behavior is simply promoted to other environments.
Cloud native components that are deployable anywhere, including developer workstations, fit this model best. With this approach, you can select the best-of-breed components with the necessary extensibility. You may find fewer choices among Platform-as-a-Service (PaaS) components and may experience blocking issues in some areas that can affect both the DX and the business.
If you are using Kubernetes, a free, lightweight ingress controller can be used as the API gateway, and you should choose one with good support for plugins. Similarly, you can run a state-of-the-art logging system such as the Elastic Stack for free. You could then also use a cloud native authorization server, such as the free community edition of the Curity Identity Server.
Fast Incident Resolution
A central DX area is your technical support process, both during and after coding. Developers may have to be on call to assist with production issues for customers. In some scenarios, organizations might be underprepared, which can be stressful for developers and may escalate to your most senior engineers, affecting future business deliverables.
OAuth security involves more moving parts than older standalone systems. Your developers must understand how to troubleshoot OAuth during coding and rehearse expiration and error conditions. From a DX viewpoint, the following setup works best, enabling any developer or tester to query logs for any component. This reduces “time to analyze,” and can minimize downtime during such events.
If you can enable the above technical support flow, issues will be managed the same way while coding as they are in production, which will help to take the stress out of it. Thus, when DevOps staff troubleshoot production incidents, they will need less developer input. Over time, this aligned people process will find more issues early and improve both DX and quality. Also, make log quality part of your selection criteria for third-party components essential to your backend.
Providing Developers with a Good Ecosystem
DX is about providing the best ecosystem for developers to improve business agility. For difficult areas such as deployment, security and incident resolution, ensure that the options you have chosen also work well on local workstations, in addition to meeting your business requirements. You will then build secure and reliable software with simpler code.
It is also a gradual journey, and technical goals must be traded against business priorities. It should be possible to articulate all DX objectives in terms of their business value, whether this is faster web development, better data protection or fewer incidents. This will help you to win support for technical initiatives and implement them gradually as part of a technical roadmap.
At Curity, we realize the importance of DX to your business. We therefore continually improve our developer resources, including guides for web, mobile and API development. The OAuth family of specifications enables you to implement many security solutions, and we also ensure that our advanced options can be run end-to-end on a development computer.