JMeter, bearer token and webdriver. "The audience is not valid" - oauth-2.0

I am working on setting up a load test suite for a site which has an OAuth2 login mechanism on another server (PKCE I believe it is), so when I click login-button I am tranfered to another site to perform the actual login, which then transfers med back to the main site.
This login site takes my credentials and in return gives the browser a code_challenge and some .js-files, from which I believe the browser later on create a Bearer token and place it in the browsers Session Storage.
This, as I understand it, cannot be replicated in JMeter alone, but it can be done if using Webdriver. Therefore I've set up a webdriver testcase in JMeter which performs the login and saves the used state, code_challenge and Bearer token in JMeter User Variables to be used later on in the load test (this all works fine).
The issue here is when I try to use the bearer token in a JMeter HTTP(S) Request I get the following error in response header:
WWW-Authenticate: Bearer error="invalid_token", error_description="The audience '<censored>' is invalid"
These are the headers for one of the GET requests with bearer token (which is previously recorded)
The bearer is collected with this piece of code
It doesn't matter if I'm using Firefox or Chrome webdrivers, the issue is the same.
So either I am trying to do something which cannot be done (re-use generated Bearer from Webdriver in JMeter), or I am missing something I do not understand. Help please?

I think you need to add Bearer to the value of the Authorization header like:
Also I don't think you need to kick off the real browser, well-behaved JMeter script is supposed to act like a real user using a real browser so my expectation is that you should be able to extract the token using HTTP Request samplers and suitable Post-Processors
For a single thread (functional test) the browser is okayish, but modern browsers are very resource intensive (one instances takes a CPU core and a couple of gigabytes of RAM) so when it comes to real load test execution you will need really powerful hardware in order to handle this authentication mechanism so it's better to stick to JMeter's HTTP Request samplers

I found the issue!
I had extracted the id_token and not access_token from the Session storage which of course was not valid as a Bearer token. They looked very much alike but was not the same, and I missed that.

Related

PKCE: Surely hacker can still steal access token?

From my understanding, the advantage that Authorization Code Flow has over Implicit Flow is that with ACF, the access token gets sent to a server side app rather than to a browser app. This makes the access token much harder to steal, because the access token never reaches the browser (and is thus not susceptible to a Cross Site Scripting attack).
I would have thought that PKCE would try to solve this issue. But it does not. The access token is still sent to the browser. Hence it can still be stolen.
Is there something I am missing here?
Many thanks.
Authorization Code Flow (PKCE) is considered superior security to the previous solution of Implicit Flow:
With implicit flow the access token was returned directly in a browser URL and could perhaps be viewed in logs or the browser history
With Authorization Code Flow this is handled better, with reduced scope for exploits:
Phase 1: A browser redirect that returns a one time use 'authorization code'
Phase 2: Swapping the code for tokens is then done via a direct Ajax request
PKCE also provides protection against a malicious party intercepting the authorization code from the browser response and being able to swap it for tokens.
Both are client side flows and their reason for existing is to use access tokens in public clients. Authorization Code Flow (PKCE) is the standard flow for all of these:
Single Page Apps
Mobile Apps
Desktop Apps
In the SPA case the token should not be easily stealable, especially if stored only in memory as recommended. However, there are more concerns when using tokens in a browser, since it is a dangerous place, and you need to follow SPA Best Practices.
In the browser case there are other options of course, such as routing requests via a web back end or reverse proxy in order to keep tokens out of the browser, and dealing with auth cookies in addition to using tokens.
I think your are right. The tokens are not in a http-only cookie, and are therefore accessible by a malicious script (injected via an XSS attack). The attacking script can read all tokens (after a successful and normal auth flow) from local storage (or wherever they got put) and use them.
I think CORS protections should prevent the malicious script from sending the tokens out to an attacker directly, which would be a devastating failure, as this potentially includes a long lived refresh token. Therefore, I suspect configuring CORS correctly is super critical when using these local-client based flows (by local-client I mean a browser, mobile app, or native PC app).
In short, these local-client flows can be made secure, but if there is an XSS attack, or badly configured CORS, then those attacks can become extremely dangerous - because the refresh token could potentially be sent to the attacker for them to use at will in their own good time, which is about as bad as an attack can get.

Cannot repeat steps on how to get OAuth2.0 Access Token on Postman

On Postman, I can get new access token for OAuth2.0 by providing callback URL, auth URL and client ID.
I want to break this task down on JMeter because I cannot find this function there. From my understanding, it is divided into authenticate -> authorise -> call back.
Authenticate
URL = https://xxxxx/login
Result = Authorising URL
Authorise
URL = https://xxxxx/oauth/authorize?client_id=mmm&redirect_uri=https://yyyyy/auth/callback&response_type=code
Result = code (e.g. zzz)
Call back
URL = https://yyyyy/auth/callback?code=zzz
Result = token
As I used HTTP(S) Test Script Recorder on JMeter, I got the three actions mentioned above. When I reran them, it told me this error on Authenticate part: <oauth><error_description>Full authentication is required to access this resource</error_description><error>unauthorized</error></oauth>.
To make sure that it was not about the program I use, I did it on Postman and found this error as well.
I wonder how I can break OAuth2.0 Get New Access Token feature into basic API settings in order to get access token on Postman or JMeter.
Dont' compare these tools:
Postman is an Electron application, it's basically a heavily customised Chromium web browser + NodeJS
According to JMeter main page
JMeter is not a browser, it works at protocol level. As far as web-services and remote services are concerned, JMeter looks like a browser (or rather, multiple browsers); however JMeter does not perform all the actions supported by browsers. In particular, JMeter does not execute the Javascript found in HTML pages. Nor does it render the HTML pages as a browser does (it's possible to view the response as HTML etc., but the timings are not included in any samples, and only one sample in one thread is ever displayed at a time).
If you can obtain the token using Postman you can just add HTTP Header Manager to your JMeter Test plan and configure it to send Authorization header with the value of Bearer YOUR_TOKEN_FROM_POSTMAN and JMeter should let you in.
After testing, I found that Postman's OAuth 2.0 Get New Access Token popped up a login page of targeted URL where I needed to fill in the username and the password so that the token could be obtained.
As I tried breaking down APIs required for this login, it required GET of that https://yyyyy and POST of that https://xxxxx/login. Click Send for POST, with username and password contained in form-data, then click Send for GET. The GET response would contain such the token.
However, just putting the aforementioned GET and POST APIs into one thread group did not work on JMeter. As I used HTTP(S) Test Script Recorder to no avail, I went with BlazeMeter and realised that it was using Transaction Controller containing 1) GET of https://yyyyy 2) POST of https://xxxxx/login. With these two arranged top-down, the job would be successful. The token was contained in the response of 2).
For now, this has been my discovery which answers my question.
Try BlazeMeter.

Is this a secure method for user authentication? If so, can it be simplified to reduce the total number of requests?

I've been digging around in Koa and had a setup which seemed to work fine. I then decided SSR would be beneficial and I'm struggling a bit with creating a method for authentication which is straightforward.
In essence the steps I am taking are:
User visits Next.JS served page.
User clicks "Login with facebook" and a request is sent to my Koa server at /auth/facebook
OAuth with passport occurs and a token is generated and stored for the user (either created then or updated)
A very short lived token is generated and the user is redirected to the Next.JS application with the short lived token in the URL.
Next.JS sends this short lived token to the Koa API and a real access token is returned and stored in a cookie.
This new access token is used for subsequent requests to the API.
This feels very complicated and I feel it might be possible to remove the short lived token step altogether.
From what I have read, it is not a good idea to use Next.JS for back-end API related logic which is why the auth happens on the Koa-API server and hence the need to pass a short lived token to get a real token.
Am I over-complicating this? Is there a simpler method that I'm just not seeing?
After a bit of fiddling around, I cut it down to only a few requests instead.
I moved Passport.js into a custom Next.JS server (using Koa) and set the callbacks to target Next. Then I verify the token on each request as it is now stored by Next.JS instead of with my API server, cutting out 4 and 5.

401 when accessing Dynamics CRM 2016 Web APIs

I am struggling to access the Dynamics 2016 CRM OData Web APIs from a console application.
We have Dynamics CRM 2016 installed, configured with Claims-based authentication, and using AD FS v3.0.
My understanding is that a console app (or web app) should be able to access the Web APIs using Windows integrated authentication (i.e. NTML or Kerberos) without any special treatment ... or maybe the OAuth flow should work when enabled.
For a regular user accessing Dynamics "pages", the authentication works fine (redirection to AD FS log in page), but accessing the OData APIs does not seem to work (for instance : https://crm.domain.org/api/discovery/v8.0/ ) :
in a browser I get a Windows login prompt and typing valid credentials always results in a HTTP 401 unauthorized error
in a brower, if I navigate to a Web API url after having logged on on the pages , then I can access the Web APIs (i.e. some cookies must be set and I am already implicitly authorized)
from code, using an HttpClient with specific valid credentials (or current credentials) , I also get a 401
Things I have tried :
if I disable Claims-based authentication completely , HttpClient works fine and I can access the OData APIs
if I leave Claims-based authentication enabled, and activate OAuth via PowerShell Add-PSSnapin Microsoft.Crm.PowerShell ; $ClaimsSettings = Get-CrmSetting -SettingType OAuthClaimsSettings; $ClaimsSettings.Enabled = $true ; Set-CrmSetting -Setting $ClaimsSettings ;.
Windows integrated authentication still does not work, but using Bearer authentication is now possible. I can use this snippet to retrieve the OAuth Endpoint for token generation, and use AuthenticationContext.AcquireTokenAsync to issue a token, and then pass it in the Authorization HTTP Header ... but then, no matter what, I get this error :
Bearer error=invalid_token, error_description
=Error during token validation!, authorization_uri=https://our.adfs.domain.org/adfs/oauth2/authorize, resource_id=https://crm.domain.org/
Am I missing something ? is that possibly a configuration issue ?
From this answer from the dynamics community forum, it looks like the api is pretty strict about the parameters and headers it requires. When doing the request, make sure you have the Cache-Control: no-cache and Content-Type: application/x-www-form-urlencoded headers set.
In the subsequent request to access the api with the retrieved token you should set the Authorization header in the form of Bearer: TOKEN (worth noting since a lot of people actually thought they could directly put the token), the OData-Version: 4.0, Cache-Control: no-cache and Accept: application/json ones too.
Looking at the different OAuth endpoints and the previously linked answer, I'm not sure the authorization uri is the right one (eg https://login.windows.net), so do you make sure that's correct. It's also stated that you should use the OAuth endpoint url and use the WWW-Authenticate header that returns the valid one, even if this route will respond with a 401. I'm sure you already saw this example, but it provides a pretty complete overview of an auth flow and how the token is retrieved using AcquireTokenAsync where you pass your resource and clientID. I might also be looking at an updated page and it's not relevant in your case.
You also want to check if the resource id you specified is the correct one, some people reported to have to specify one in the form of https://crm3.domain.org/ or https://crm4.domain.org/ instead of the bare one, so that could be one thing.
It could also be a configuration issue, given what #l said about the fact an IP would work instead of the domain name. It could very well be a certificate problem, where it's not validated correctly or untrusted, thus creating the error you see even if it's not the appropriate message. Also make sure your 443 port is allowed through your firewall(s).
One interesting post where the author explains that the Form Authentication setting of the AD FS Management Console was required for him to proceed (it's CRM 2013, but might still be related).

No browser is sending Authorization info in header

I'm looking at this and this and it would appear 'easy' to send the credentials in the URL. For example:
http://gooduser:secretpassword#www.example.com/webcallback?foo=bar
This is all well and good but it doesnt work. I've turned fiddler on and for Chrome the Authorization header isnt sent. It appears to exhibit the same behaviour for other browsers (i've got a breakpoint on the server and no Authorize header turns up for Firefox,Safari or IE either)
How to make it better?
Stumbled across this while researching various basic auth implementations.
Browsers generally only send basic auth if they receive a 401 challenge response from the server (more on basic auth protocol). If the endpoint in question accepts both authenticated and non-authenticated users, then a browser-based request will likely never be prompted for the auth parameters.
The easiest way to test this type of setup is to send a curl request (which sends the authentication parameters regardless) to your server endpoint and validate receipt of the authorization header:
curl 'http://gooduser:secretpassword#www.example.com/webcallback?foo=bar'
OK so after much searching and experimenting the approach
http://gooduser:secretpassword#www.example.com/webcallback?foo=bar
does work.
However one needs to be careful to ensure that the secret password DOES NOT contain any special characters. Use a password containing only letters and numbers and a hypen(if you must) and it should work.

Resources