You want Modern Auth … for an app or client that’s stuck in the 2010s
Anyone involved with IT for a considerable time understands that change is inevitable and perpetual. Organizational requirements shift and adapt to emerging technology, security threats, and new methods of operating. As architects, engineers, and developers align their roadmaps with corporate objectives, solutions are needed to ensure seamless operations. Sometimes this means solutions must be created that cater to teams with both legacy and strategically positioned technical solutions.
A great example of this situation is user authentication. Some teams can quickly adapt to new technologies and remain strategic with their operations. However, some teams may be constrained and unable to shift, leading to unexpected behaviour or even complete loss of service.
Authenticating users is nothing new, but the ways the industry goes about doing so have shifted over the years. The days of simply presenting a username and password are fading as solutions like JWT token minting become more prevalent. Not all clients can support token minting, though, and this presents a real problem when organizational requirements clash with a client's capabilities. To fully set the scene, it would be beneficial to review Basic Auth and JWT at a high level, look at how they are used, and why JWT is becoming the preferred authentication method.
Basic Auth has been utilized for years and allows credentials to be presented to an endpoint via the “Authorization” HTTP header. The contents of the header typically take the form of a Base64 encoded username and password combo. The header value is then decoded by the server and determines whether the credentials are valid. You can easily see the user info in the header as it is simply Base64 encoded. While this is a reliable and proven solution for authenticating users, some security and scalability concerns exist. For example, if the user was “simpleauth” and the password was “f5@pmr0cks”, you would encode this as c2ltcGxlYXV0aDpmNUBwbXIwY2tz. To try this yourself, visit https://base64encode.org to encode or decode values.
At first glance, this seems “secure” since it’s “encrypted,” but it’s just an encoding of simpleauth:f5@pmr0cks, and anyone reading this request can get the username and password. It would be better to log into a system that generates some time-based token that we can revoke without revoking the existing account that creates it.
Enter OAuth and JWT tokens…
JWTs offer several advantages over providing traditional credentials through Basic Auth. One of the inherent security features revolves around time-limited tokens and is subject to expiration. With this type of rapid cycling of authentication data, intercepting and re-using a JWT token is very difficult and not likely to be productive. Another critical feature of JWTs is their ability to be issued by one provider and verified by another. This means the same token can be verified by services which were not responsible and can’t create the token itself. Imagine the extensibility this provides!
Let's say you are responsible for the application delivery of many different APIs. Users access the APIs through a BIG-IP, which performs load-balancing to back-end servers. Until recently, users have been leveraging Basic Auth to validate their access to the estate. Leadership has decided all authentication must be performed via JWT token, though, and a solution needs to be put in place. Unfortunately, pretty early in the process, you realize that not all users can obtain a JWT token. This means you must accommodate two scenarios:
- Users who can provide their token for validation
- Users who can provide Basic Auth and need a token minted for them
Fortunately, F5 BIG-IP Access Policy Manager provides the tools to accomplish this with relative ease and great flexibility. One of the critical components of this solution is the HTTP Connector, which was introduced in BIG-IP v15.1. The HTTP Connector enables administrators to initiate HTTP requests to endpoints from BIG-IP APM’s access policies. The response can then be processed and parsed via iRule.
This may seem like a simple operation -- because it is. However, simplicity does not necessarily diminish usability or value. In fact, it is the simple nature of this feature which opens the door to such a wide variety of use cases. Having the flexibility to create your own custom HTTP request allows administrators to implement these sideband types of callouts to any number of external systems. Let that thought percolate for a moment, and we’re sure you’ll start brewing up some use cases!
Let's do this...
The TL;DR of this process is:
- A client request is received with a Basic Authorization header
- The Basic Auth header is read from the request and stored
- APM Session is checked for an existing, valid JWT for this Basic Auth. If none exists, Basic Auth is sent to an API as POST data, API returns data containing JWT token and expiry if successful
- JWT is stored in the BIG-IP APM session table with expiry for future comparison
- The authorization header on the request is set to the JWT token and forwarded
- A client request is received with an OAuth Authorization header
- The request is forwarded to the pool member
A couple of items for clarification:
- The generation of the JWT token is beyond the scope of this document but is handled by an external API that rotates and caches tokens in the interest of processing time
- The BIG-IP APM Session table is a secure storage location, protected by the Secure Vault infrastructure on the BIG-IP
- For API calls to work properly with BIG-IP APM and not have requests redirected, Clientless Mode needs to be enabled. The iRule added this header if it doesn’t exist.
- For this config, we will use both a Per Session Policy (PSP) and a Per Request Policy (PRP) which will allow us to monitor each individual web request.
Per Session Policy
The PSP is a container for us to work in, with a Start->Allow framework, with some logging added for good measure.
However, the Session itself is important because we use this as a safe, secure storage location for the data we retrieve from the API (see below for more details).
Per Request Policy
The meat of the functionality is built within a PRP and an attached iRule. The iRule is called throughout the PSP using an iRule PRP Event (ACCESS_PER_REQUEST_AGENT_EVENT) with specific IDs for the various spots in the policy. The AgentId is scanned for a value, and the code associated with the proper ID is executed.
To handle the flow within the Policy, the perflow.custom Per Request variable is updated when the AgentId is completed.
Output
get-bearer iRule event with exit rules configured to look at the perflow.custom variable
Agent ID: post-header:
This Agent ID is here for tracking the initial condition of the PSP and dumps out the headers for demonstrating the headers having been changed.
Output
perflow.custom is left untouched
Agent ID: get-bearer:
The Get-Bearer agent ID is for attempting to pull the token from the Session variables to allow it to be added to the Authorization header.
Output
perflow.custom is set to “” if the Token existed but has expired, meaning we need to execute the HTTP Connector call.
perflow.custom is set to “bearer-exists” if the Token existed and has not yet expired at the evaluation time.
Agent ID: store-bearer:
This Agent ID is where the bearer token is retrieved from the Authorization Service. In this use case, these Tokens are minted and then stored with an expiration time. The token data (the token, Basic Auth string, expiry time and the current time) are saved within the Session data. Note this is _Session_ data, not ‘Per Request’ data, as we want to have this information available to us in subsequent calls in this session.
Output
store-bearer saves the data in the APM session and perflow.custom is set to “exists”
Note that in this section, there are a couple of commented sections which address some iRule differences between BIG-IP versions. In versions before 16.0, the HTTP Connector returns it’s results in subsession.http_connector.body.<parameter> when the HTTP Connector request is set to parse the JSON payload. For more details, see the configuration for the HTTP Connector further along in this article. For versions before 16.0, the iRule will extract the necessary items from the whole body using a regular expression.
Configuring HTTP Connector
Part of the key functionality of this solution is the ability to make outbound HTTP calls on each request sent to the APM-enabled VIP. HTTP Connector requires some configuration of an HTTP Connector Request and HTTP Connector Transport.
HTTP Connector Transport
This requires a DNS Resolver to be established. A DNS Resolver allows tmos to make in-request DNS resolutions; note that this will originate from the Management IP so that you will require some routes to this name server.
DNS Resolver
Note that you can likely use a “.” and a DNS Name Server that the BIG-IP has access to. In some instances, the hostname might require a specific “internal” Name Server, and this can be handled by adding the internal domain and the internal Name Server as shown here with the hidden .2.11 / .2.12 addresses resolving the internal “xxxnet.net”. If there is another existing DNS Resolver for other configurations, it can be reused in this configuration.
HTTP Connector Request
The HTTP Connector Request has the configuration where the request will be sent. There will be some URLs (partially shown here) with a method and authentication (if required). The Request Headers can be customized, as here, with a specific User-Agent and Content-Type.
In this case, the Basic-Auth has been extracted by the iRule into the perflow.custom Session Variable is passed to the URL as a POST. The response from this API call is a JSON with the new Bearer Token. This Requestor is set to Parse the JSON response body into session variables.
Branching based on Authorization Header Contents
If the incoming request has a Bearer token, this element in the Policy will switch to the branch, which simply logs this fact and passes the request to the pool member.
Setting the Request Authorization Header
Once the JWT token has been established, either from the Session data or the API call, it needs to be added to the request. The existing request already has Basic Auth in the Authorization header, so the HTTP Headers object in the VPE is configured to Replace the header with the new JWT stored in the Session (i.e. %{session.poc.token.access_token} )
Logging
For explanation and tracking, logging has been added throughout the Policy. We have left this in the examples so the reader can see how we utilized logging to message back to BIG-IP APM the status of each step.
Careful attention should be made to using this in any environment where exposing the logs and tokens can compromise data security.
Log the Token in the BIG-IP logs
Warn the administrator that an existing Bearer token was received in the client's request
Connecting it all up
See what we did there? 😉
This implementation is an example of how HTTP Connector can be utilized in your environment. The ease and flexibility of HTTP Connector opens many doors to how you can authenticate incoming requests … how are you going to use the HTTP Connector?
The source for this example is available here https://github.com/pmscheffler/sidebandauth
- AlexS_ybCirrocumulus
Hi
Okay - so you are just passing a JWT through the F5 to the backend ... Reason i am asking is I am trying to use a JWT to login into a APM session, hoping to see how that was done.
Still not 100% clear to me how perflow.custom i had presumbed it was with the irules. is it in the Git repo ?
Thanks
- ScheffEmployee
The iRule is called from the 'iRule Event' item in the PRP Policy. Each of these items has a different ID that is sent to the iRule and evaluated in the switch statement on line 6 (inside the ACCESS_PER_REQUEST_AGENT_EVENT code).
As for validating the JWT, that's beyond the scope of the requirement, the JWT here was minted by an outside source and then is provided to the BIG-IP based on the Basic Auth Username and Password.
- AlexS_ybCirrocumulus
Hi
looks good, Question
The meat of the functionality is built within a PRP and an attached iRule. The iRule is called throughout the PSP using an iRule PRP Event (ACCESS_PER_REQUEST_AGENT_EVENT) with specific IDs for the various spots in the policy. The AgentId is scanned for a value, and the code associated with the proper ID is executed.
Can't find this code or the handle from the PSP or PRP to call the irule code.
Where do you validate the JWT and how do you create a APM session and login based upon a JWT