-
-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Auth0/Okta support #656
Comments
I tried changing qs: {
redirect_uri : window.location.origin,
client_id: 'Tz7nrQ36DkcKmtuDMTZfi76tVMn7LDge',
response_type: 'code',
scope,
audience,
}, I had to add |
@mraible I am wondering if we should create a package |
That's not a bad idea. We could also read the |
From your screenshot:
The code should be extracted from the redirects. |
That worked! diff --git a/ionic-app/cypress/integration/login.e2e-spec.ts b/ionic-app/cypress/integration/login.e2e-spec.ts
index 5ffe035..dd5ab50 100644
--- a/ionic-app/cypress/integration/login.e2e-spec.ts
+++ b/ionic-app/cypress/integration/login.e2e-spec.ts
@@ -18,7 +18,7 @@ describe('Login', () => {
cy.login(ADMIN_USERNAME, ADMIN_PASSWORD);
cy.visit('/');
- const welcome = /Welcome, Admin/;
+ const welcome = /Welcome, Matt/;
cy.get('app-home ion-title').invoke('text').should('match', welcome);
});
diff --git a/ionic-app/cypress/support/oauth2.ts b/ionic-app/cypress/support/oauth2.ts
index 4bbf6ae..2a802c7 100644
--- a/ionic-app/cypress/support/oauth2.ts
+++ b/ionic-app/cypress/support/oauth2.ts
@@ -7,7 +7,6 @@ import { environment } from '../../src/environments/environment';
const {
oidcConfig: { redirect_url: redirect_uri, scopes: scope, audience },
} = environment;
-
// Get oauth2 basic data
const getOauth2Data = () =>
cy
@@ -26,7 +25,7 @@ const getOauth2Data = () =>
info,
configuration,
qs: {
- redirect_uri,
+ redirect_uri : window.location.origin,
client_id: 'Tz7nrQ36DkcKmtuDMTZfi76tVMn7LDge',
response_type: 'code',
scope,
@@ -136,9 +135,8 @@ const oauthLogin = (username: string, password: string) => {
} else {
authorizeCode = keycloakLogin(oauth2Data, username, password);
}
- authorizeCode.then(({ headers }) => {
- const { location } = headers;
- const locationUrl = new URL(location as string);
+ authorizeCode.then(({ redirects }) => {
+ const locationUrl = new URL(redirects.pop().split(' ').pop());
const code = locationUrl.searchParams.get('code');
// Retrieve token.
@@ -151,8 +149,8 @@ const oauthLogin = (username: string, password: string) => {
grant_type: 'authorization_code',
code,
refresh_token: undefined,
- redirect_uri,
- client_id: clientId,
+ redirect_uri: window.location.origin,
+ client_id: 'Tz7nrQ36DkcKmtuDMTZfi76tVMn7LDge',
},
}).then(({ body }) => {
localStorage.setItem('CapacitorStorage.token_response', JSON.stringify(body)); I'll create a PR. We might want to make it so the client ID is always looked up from |
I've determined that Okta is quite a bit more difficult. Since the token endpoint requires PKCE, you have to send a You can see an overview of PKCE and how it works in https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce. A plain-JavaScript example can be found at https://github.com/aaronpk/pkce-vanilla-js/blob/master/index.html. I'm going to add a bug bounty to this issue since it's more work than expected. It should be worth it though, PKCE should work with Keycloak, Auth0, and Okta. That's what Ionic AppAuth uses. |
Reopening since Okta isn't working yet. |
@mraible you may want to test the login using cy.origin. generator-jhipster-ionic/generators/ionic/resources/oauth2/cypress/support/commands.ts Lines 29 to 46 in 8f8597e
|
@mshima I tried using this and it doesn't work. It gets to the Okta login form and fails to fill in the fields. I tried changing the field names to match the Okta form, but it doesn't help. cy.get('input[name="identifier"]').type(username);
cy.get('input[name="credentials.passcode"]').type(password); |
I wonder if we could use AppAuth JS to do the OAuth dance for us? Here's an example that works in Electron. https://github.com/oktadev/okta-appauth-js-electron-example/blob/master/flow.ts |
I took a look at it first. IMO we should wait for some days if there are some movement to fix the cypress origin. |
I tried the following, but it just hangs when waiting for const login = (username: string, password: string) => {
cy.session(
[username, password],
() => {
cy.visit('/');
cy.get('#signIn').click();
cy.url({ timeout: 10000 }).should('includes', '/okta/');
cy.url().then(url => {
const { protocol, host } = new URL(url);
// eslint-disable-next-line @typescript-eslint/no-shadow
cy.origin(`${protocol}//${host}/`, { args: { url, username, password } }, ({ url, username, password }) => {
// Reload oauth2 login page due to cypress origin change.
cy.visit(url);
cy.get('input[name="identifier"]').type(username);
cy.get('input[name="credentials.passcode"]').type(password);
cy.get('input[type="submit"]').click();
});
});
cy.url({ timeout: 10000 }).should('eq', Cypress.config().baseUrl + 'tabs/home');
},
{
validate: () => {
cy.authenticatedRequest({ url: '/api/account' }).its('status').should('eq', 200);
},
}
);
}; |
By the way, I've created that project as a repro for cypress-io/cypress#21201 (comment). |
YAHOO - that worked! const login = (username: string, password: string) => {
cy.session(
[username, password],
() => {
cy.visit('/');
cy.get('#signIn').click();
// cy.url({ timeout: 10000 }).should('includes', '/okta/');
cy.url().then(url => {
// eslint-disable-next-line @typescript-eslint/no-shadow
cy.origin(`https://dev-17700857.okta.com/oauth2/default/v1/authorize`, { args: { url, username, password } }, ({ url, username, password }) => {
cy.get('input[name="identifier"]').type(username);
cy.get('input[name="credentials.passcode"]').type(password);
cy.get('input[type="submit"]').click();
});
});
cy.url({ timeout: 10000 }).should('eq', Cypress.config().baseUrl + 'tabs/home');
},
{
validate: () => {
cy.authenticatedRequest({ url: '/api/account' }).its('status').should('eq', 200);
},
}
);
}; |
Great, the bug is with Keycloak them. |
So we need to get the url from auth-info and then try to use it at the |
Something like: import { getOauth2Data } from './oauth2';
const login = (username: string, password: string) => {
cy.session(
[username, password],
() => {
getOauth2Data().then(oauth2Data => {
const {
configuration: { authorization_endpoint },
} = oauth2Data;
const { protocol, host } = new URL(authorization_endpoint);
cy.visit('/');
cy.get('#signIn').click();
// eslint-disable-next-line @typescript-eslint/no-shadow
cy.origin(`${protocol}//${host}`, { args: { username, password } }, ({ username, password }) => {
cy.get('input[name="identifier"]').type(username);
cy.get('input[name="credentials.passcode"]').type(password);
cy.get('input[type="submit"]').click();
});
)};
cy.url({ timeout: 10000 }).should('eq', Cypress.config().baseUrl + 'tabs/home');
},
{
validate: () => {
cy.authenticatedRequest({ url: '/api/account' }).its('status').should('eq', 200);
},
}
);
}; |
This works for Keycloak, Okta, and Auth0! /* eslint-disable @typescript-eslint/naming-convention, @typescript-eslint/no-namespace */
import { apiHost } from './config';
const authenticatedRequest = (data: any) => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const tokenResponse = localStorage.getItem('CapacitorStorage.token_response');
if (!tokenResponse) {
return Cypress.Promise.reject('token_response is missing');
}
const {access_token, token_type} = JSON.parse(tokenResponse);
return cy.request({
...data,
url: apiHost + data.url,
headers: {
...data.headers,
// eslint-disable-next-line @typescript-eslint/naming-convention
Authorization: token_type !== 'Bearer' ? access_token : `Bearer ${access_token}`,
},
});
};
const getOauth2Data = () =>
cy.request({
url: `${apiHost}/api/auth-info`,
followRedirect: false,
}).then(({body: info}) => {
const {issuer} = info;
return cy
.request({
url: `${issuer.replace(/\/$/, '')}/.well-known/openid-configuration`,
followRedirect: false,
})
.then(({body: configuration}) => ({
configuration
}));
});
const login = (username: string, password: string) => {
getOauth2Data().then(oauth2Data => {
const {configuration: {authorization_endpoint}} = oauth2Data;
cy.session(
[username, password],
() => {
cy.visit('/');
cy.get('#signIn').click();
cy.origin(authorization_endpoint, {
args: {authorization_endpoint, username, password}
// eslint-disable-next-line @typescript-eslint/no-shadow
}, ({authorization_endpoint, username, password}) => {
let usernameElement = 'username';
let passwordElement = 'password';
if (authorization_endpoint.includes('okta')) {
usernameElement = 'identifier';
passwordElement = 'credentials.passcode';
}
cy.get(`input[name="${usernameElement}"]`).type(username);
cy.get(`input[name="${passwordElement}"]`).type(password);
cy.get('[type="submit"]').first().click();
});
cy.url({timeout: 10000}).should('eq', Cypress.config().baseUrl + 'tabs/home');
},
{
validate: () => {
cy.authenticatedRequest({url: '/api/account'}).its('status').should('eq', 200);
},
}
);
});
};
Cypress.Commands.addAll({
authenticatedRequest,
login,
});
declare namespace Cypress {
interface Chainable<Subject = any> {
login(username: string, password: string): typeof login;
authenticatedRequest<T = any>(options: Partial<RequestOptions>): typeof authenticatedRequest;
}
} |
PR to incorporate these changes: #660 |
The Keycloak issue was confirmed on cypress side. Should be followed at cypress-io/cypress#20685. |
We could recommend ngrok or other solutions for https with Keycloak. |
References:
#653 (comment)
#653 (comment)
#653 (comment)
Implementations:
generator-jhipster
svelte blueprint
The text was updated successfully, but these errors were encountered: