Cypress: Login Authentication redirects to another domain: Workaround? - authentication

I understand that Cypress does not allow flipping from one domain to another domain because it will error with:
chrome-error://chromewebdata/
However, I need a workaround. I am providing a test set for multiple environments: STAGE, DEMO, PROD.
With DEMO and PROD, during the authentication phase (username/password), stay within the same domain:
VISIT: https://[demo|www].foo.com
AUTH: https://account.foo.com/auth >> username >> password
CONSENT: https://[demo|www].foo.com/action...
With STAGE, the authentication phase flips to another domain:
VISIT: https://[stage].foo.com
AUTH: https://account.bar.com/auth >> username >> password
CONSENT: https://[stage].foo.com/action...
Thereby, Cypress fails to redirect from VISIT to AUTH because of domain flip. This is blocking testing of STAGE.
What recommended workaround approaches?
Puppeteer?
Native Cypress using cy.request()?
Referenced:
Handling Cypress url redirect
Error with authentication in e2e tests using cypress: chrome-error://chromewebdata
Thank you, much appreciate the assistance.

The approach I took is similar to the Cypress Recipe Login with CSRF token
As mentioned, each deployment has its own website URL followed by account login URL.
Needed first is the CSRF token from account login page, which its URL is referred here as $baseUrl:
Cypress.Commands.add('cmdGetCSRF', ($baseUrl) => {
cy.log('COMMAND cmdGetCSRF');
expect($baseUrl)
.to.be.a('string')
.not.empty
cy.request($baseUrl)
.its('body')
.then(($body) => {
const $html = Cypress.$($body)
cy.log('html', $html)
const csrf = $html.find('input[name="__RequestVerificationToken"]').val()
expect(csrf)
.to.be.a('string')
.not.empty
return cy.wrap(csrf)
})
})
Then within body of requestOptions provided to cy.request() which will performing the login, provide one-time CSRF token:
const body: { [key: string]: string } = {
email: $envCypress.account_username,
password: $envCypress.account_password,
__RequestVerificationToken: $csrfToken
};

Cypress has recently add a new experimental feature for running test on multiple origins here. There are examples for cross origin login too. More on experimentalSessionAndOrigin feature:
https://docs.cypress.io/api/commands/origin
https://docs.cypress.io/api/commands/session

Related

Heroku Express / Nextjs client cookie not being set

So I'm having a bit of an issue where I have two apps hosted on Heroku the first being an Express application with the following cookie settings
const cookieSettings = {
maxAge: expiryTime,
...cookieOptions || {},
// For security these properties should always remain below the spread
httpOnly: true,
secure: process.env.NODE_ENV !== "development",
sameSite: "none",
path: "/",
}
And a Nextjs app which has some middleware that uses the cookie for login control to protect routes.
Locally I have no issues with the following login logic within the login route which sets the cookie browser side
const cookie = getCookie({ tokenOptions: { id: user._id } });
res.setHeader("Set-Cookie", cookie);
return res.sendStatus(200);
I have read there is issues with Heroku as it's on the public list of domains so the browser wont set if it comes from this but the issue I'm having is on a custom domain. My domain is https://www.mydomain.co.uk but for some reason I can't get it to set when I'm on this domain.
When using Postman I do get the cookie back but the domain comes from my API domain ie api.reviewcircle.co.uk which I think is why the browser isn't setting the cookie as the domains don't match but I can't find any info on how to fix this so would be great if anyone has any ideas.
You can see the cookie is included in the response but isn't set:

invalid_grant exchanging authorization code for access and refresh tokens

My application is using OAuth to access the Youtube Data API. My OAuth callback is written in node and uses the OAuth2Client class from the "googleapis" npm package to exchange the authorization code for the access and refresh tokens.
Everything was working fine up to last week until suddenly I started getting the "invalid_grant" response during the authorization code exchange. I have tried everything to resolve this and am running out of ideas. My callback executes as a cloud function so I don't think that it would be out of sync with NTP.
My OAuth consent screen is in "Testing" mode and my email address is included in the test users. The odd thing is that even though the authorization code exchange fails, my Google account's "Third-party apps with account access" section lists my application as if the handshake succeeded.
Is there a limit to how many refresh tokens can be minted for my application? I am testing my implementation of incremental authorization so I have been going through the OAuth flow often.
Edit
I've included my code for generating the auth URL and exchanging the authorization code below. The invalid_grant occurs during the call to "oauth2.getToken"
async startFlow(scopes: string[], state: string): Promise<AuthFlow> {
const codes = await oauth2.generateCodeVerifierAsync();
const href = oauth2.generateAuthUrl({
scope: scopes,
state,
access_type: 'offline',
include_granted_scopes: true,
prompt: 'consent',
code_challenge_method: CodeChallengeMethod.S256,
code_challenge: codes.codeChallenge
});
return { href, code_verifier: codes.codeVerifier };
}
async finishFlow(code: string, verifier: string): Promise<Tokens> {
const tokens = await oauth2.getToken({ code, codeVerifier: verifier })
return {
refresh_token: tokens.tokens.refresh_token!,
access_token: tokens.tokens.access_token!,
expires_in: tokens.tokens.expiry_date!,
token_type: 'Bearer',
scopes: tokens.tokens.scope!.split(' ')
};
}
"oauth2" is an instance of OAuth2Client from "google-auth-library". I initialize it here:
export const oauth2 = new google.auth.OAuth2({
clientId: YT_CLIENT_ID,
clientSecret: YT_CLIENT_SECRET,
redirectUri: `${APP_URI}/oauth`
});
Looking at the logs, the only out of the ordinary thing I notice is that the application/x-www-form-urlencoded body looks slightly different than the example https://developers.google.com/identity/protocols/oauth2/web-server#exchange-authorization-code
The POST request to "https://oauth2.googleapis.com/token" ends up looking like this:
code=4%2F0AX4XfWiKHVnsavUH7en0TywjPJVRyJ9aGN-JR8CAAcAG7dT-THxyWQNcxd769nzaHLUb8Q&client_id=XXXXXXXXXX-XXXXXXXXXXXXXXX.apps.googleusercontent.com&client_secret=XXXXXX-XXXXXXXXXXXXXXX-XX_XXX&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth&grant_type=authorization_code&code_verifier=KjOBmr4D9ISLPSE4claEBWr3UN-bKdPHZa8BBcQvcmajfr9RhWrgt7G429PLEpsP7oGzFGnBICu3HgWaHPsLhMkGBuQ2GmHHiB4OpY2F0rJ06wkpCjV2cCTDdpfRY~Ej
Notice that the "/" characters are not percent-encoded in the official example, but they are in my requests. Could this actually be the issue? I don't see how the official google auth library would have an issue this large.
The most common cause for the invalid_grant error is your refresh token expiring.
If you check oauth2#expiration you will see the following
A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.
Once you set your project to production your refresh tokens will stop expiring.
Is there a limit to how many refresh tokens can be minted for my application?
No but you have a limit of 100 test users.

How to login with HTTP basic authentication to ACCESS the website- Test Cafe with Gherkin and cucumber

All links, project name and company name has been modified and are not original.
We have a basic HTTP authentication popup that appears when we are accessing out test/staging environments.
I am wondering, how can I enter the login information into the popup login window or send an api request in advance to save the cookie?
Because otherwise, I can't run the automation on our test environment. When the test access the page, I see a white website showing "unauthorized" in small letters.
This is what the login prompt looks like when accessing the Test/Staging env
We are using following plugin: https://www.npmjs.com/package/gherkin-testcafe
What I am asking is very similar to this question: Testing http basic authentication with Cucumber and RSpec in Rails 3.2.3
I have tried adding a role and using TestCafe http-authentication.html
Here is what I have tested so far:
TestCafe trying to use role:
const { Role } = require('testcafe');
const regularAccUser = Role('https://website/login', async t => {
await t
.typeText('#login', 'username')
.typeText('#password', 'password')
.click('#sign-in');
});
Given("I am open Dealer's login page", async t => {
await t
.useRole(regularAccUser)
.navigateTo(`${url}/login`);
});
That gives me:
ERROR CompositeParserException: Parser errors:
(7:3): expected: #EOF, #Comment, #BackgroundLine, #TagLine, #ScenarioLine, #ScenarioOutlineLine, #Empty, got 'Correct action happens when user provide either wrong or correct login information'
at Function.Errors.CompositeParserException.create (/Users/dennis/Projects/src/company/project/node_modules/gherkin/lib/gherkin/errors.js:27:13)
at Parser.parse (/Users/dennis/Projects/src/company/project/node_modules/gherkin/lib/gherkin/parser.js:72:45)
at specFiles.forEach.specFile (/Users/dennis/Projects/src/company/project/node_modules/gherkin-testcafe/src/compiler.js:43:33)
at Array.forEach (<anonymous>)
at GherkinTestcafeCompiler.getTests (/Users/dennis/Projects/src/company/project/node_modules/gherkin-testcafe/src/compiler.js:42:20)
at getTests (/Users/dennis/Projects/src/company/project/node_modules/testcafe/src/runner/bootstrapper.js:79:47)
at Generator.next (<anonymous>)
at step (/Users/dennis/Projects/src/company/project/node_modules/babel-runtime/helpers/asyncToGenerator.js:17:30)
at /Users/dennis/Projects/src/company/project/node_modules/babel-runtime/helpers/asyncToGenerator.js:28:13
If I try using:
import {Role} from 'testcafe'
I get:
tests/ui/stepDefinitions/login/login.js:1
(function (exports, require, module, __filename, __dirname) { import { Role } from 'testcafe';
^
SyntaxError: Unexpected token {
Using TestCafe's HTTP Authentication:
Feature: Login with correct and wrong info functionallity
.page('website/login')
.httpAuth({
username: 'username',
password: 'password',
})
Correct action happens when user provide either wrong or correct login information
#loginFunc
Scenario: Should NOT be able to login without filling in any credentials
Given I am open Dealer's login page
When I am login in with "empty" and "empty"
Then I should NOT be able to press the login button
I am getting following:
Feature: Login with correct and wrong info functionallity
✖ Scenario: Should NOT be able to login without filling in any credentials
1) Cannot obtain information about the node because the specified selector does not match any node in the DOM tree.
  | Selector('button[data-qa="qa-login-submit-button"]')
 > | Selector('button[data-qa="qa-login-submit-button"]')
Browser: Chrome 72.0.3626 / Mac OS X 10.14.3
32 | }
33 |});
34 |
35 |Then("I should NOT be able to press the login button", async t => {
36 | await t
> 37 | .expect(submitButton.hasAttribute('disabled')).ok()
38 | .expect(h1.exists).eql(true);
39 |});
```
It is basically showing me a white screen and saying: "**unauthorized**" in small letters.
You don't need any post/get requests with the Basic HTTP. Instead, you can log in using the Role mechanism:
When you switch to a role for the first time, TestCafe internally creates a branch of this role for this particular test run. All cookies set by your further actions will be appended to this branch. This branch will be used whenever you switch back to this role from the same test run.

Feathers - Authentication and Authorization

I have created an app using Feathers. I've been using this app for a while. It successfully hosts a blog and some other web pages. However, I've now reached a point where I need to protect some of my routes. For example, I want to have a route for my administrative activitivies (/admin), but I only want specific users to have access.
I know that I need to use the authentication and authorization components. However, at this time, I'm stuck on the authorization. My goal is to authenticate using OAuth via Google. However, to get past my authentication challenge, I'd be happy with just using a hard-coded username / password just to get the /admin route locked down (no, it's not deployed).
At the moment, I have
const app = feathers();
const routes = require('./routes');
app.configure(configuration(path.join(__dirname, '..')));
app.use(compress())
.options('*', cors())
.use(cors())
.use(favicon( path.join(app.get('public'), 'favicon.ico') ))
.use('/public', serveStatic(app.get('public'), staticFileSettings ))
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: true }))
.configure(routes)
.configure(hooks())
.configure(rest())
.configure(socketio())
.configure(services)
.configure(middleware)
.configure(authentication())
;
// Setup the authentication strategy.
app.authenticate({
type: 'local',
'email': 'admin#feathersjs.com',
'password': 'admin'
}).then(function(result){
console.log('Authenticated!', result);
}).catch(function(error){
console.error('Error authenticating!', error);
});
My problem is, as soon as I add the block of code with the app.authenticate stuff, I get an error when I start my app. The error says:
TypeError: app.authenticate is not a function
If I remove app.authenticate(...); My app starts fine, but nothing is locked down. In my ./routes/index.js file, I have:
app.use('/admin', function(req, res) {
res.render('admin/index.html', {});
});
Which, renders just fine. It's just not restricted to an authenticated and authorized user. What am I missing? At a bare minimize I'm trying to understand how to get past the app.authenticate error.
In order to protect a route from unauthorized access you need to follow the documented usage of express middleware provided by the feathers-authentication package that is installed when you do feathers generate authentication.
Here's an example of authenticating the /admin route.
const auth = require('feathers-authentication');
app.use(
'/admin',
auth.express.authenticate('jwt'), // <-- this is a strategy, can local/jwt... etc
(req, res, next) => {
console.log("Request for '/admin'...");
res.render('admin');
}
);

Postman localhost Authentication

I created an API with Laravel 4 that I want to test so I'm using postman. When I input my username, my password and my URL:
http://127.0.0.1/stores/public/locator/api/service/v1/store
A popup shows and tells me to enter my username and password for localhost:80. How do I solve this problem?
Here is the route :
Route::resource('store', 'StoreController');
Route::filter('Apiauth', function()
{
return Auth::basic("username");
);
Route::group(array('prefix' => 'locator/api/service/v1', 'before'=> 'Apiauth'), function()
{
Route::resource('store', 'StoreController');
});
You can include basic authentication in
http://username:passwordd#www.server.com/route
HTTP Basic Authentication credentials passed in URL and encryption