Keycloak redirect_fragment conflicts with react-router hashRouter - react-router

I'm running into an issue with the redirection that happens after a user of my app authenticates with Keycloak.
My app uses react-router hashRouter. When the initial redirect happens, I get a redirect_fragment that looks something like this:
http://localhost:3000/lol.html?redirect_fragment=%2F&redirect_fragment=%2Fstate%3D1c5900ee-954f-4532-b01c-dcf5d88f07a2%26code%3DKZNXVqQCcIXTCFu2ZIkx4quXa6zJb59zGKpNIhZwfNo.d2786d1e-67cd-437f-a873-bad49126bad4&redirect_fragment=%2Fstate%3D51a9cb44-b80a-4c14-8f3d-f04dfdb84377%26code%3Dp5cKQ7xVCR_n1s4ucXZTSE3O1T5lwNri_PBKD07Mt1Y.63364a83-f04f-4e64-a33e-faf00f6cd4ff&redirect_fragment=%2Fstate%3D05155315-ab60-4990-8d4e-444c7cce9748%26code%3DBxxpf_uMB28rKAQ6MXFTTrL9RE4rC3UtwCMXLu_K1Zo.4ce56da0-8e52-47e3-a0f2-4f982599bb98#/state=f3e362e4-c030-40ac-80df-9f9882296977&code=8HHTgd3KdlfwcupXR_5nDV0CqZNPV1xdCu3udc6l5xM.97b3ea71-366a-4038-a7ce-30ac2f416807
The URL keeps growing from there. I've read a few posts already that indicate that redirection from keycloak might have a problem with client-side routing via location.hash ... Any thoughts would be appreciated!

I think I figured it out!
The redirection loop seems to stop if I use the 'noslash' hashRouter instead of the default which contains a slash.
My URLs look like this: localhost:3000/lol.html#client/side/route
instead of this: localhost:3000/lol.html#/client/side/route
The redirection now seems to terminate appropriately after one redirect, but now I'm running into a different problem where the hash portion of my route is not being honored by react-router...
EDIT: I figured the second issue out
react-router creates a wrapper around window.location that it uses to tell which client side "page" it is currently on. I found that this wrapper was out of sync with window.location.
Check this console output out. This was taken immediately after the redirection resolved (and the page was blank):
history pathname is /state=aon03i-238hnsln-soih930-8hsdlkh9-982hnkui-89hkgyq-8ihbei78-893hiugsu
history hash is (empty)
window.location pathname is /lol.html
window.location hash is #users/1
The state=blah-blah-blah in the history.pathname is part of the redirect url that keycloak sends back after auth. You'll notice that window.location is updated to the correct path / hash, but that history seems to be one URL behind. Maybe keycloak directly modifies window.location to perform this redirection?
I tried using a history.push(window.location.hash) to push the hash fragment and update react router, but got the error "this entry already exists on the stack". Since it clearly is not on the top of the location stack, this led me to believe that react-router compares window.location with its internal location to figure out where it ultimately is. So how did I get around this?
I used history.replace() instead, which just replaces the entry on the top of the stack with a new value, instead of pushing a new entry to the stack. This also makes sense, since we don't want users who navigate "back" in their browsers to go back to that /state=blah-blah-blah url <-- replace eliminates this entry from the history stack.
One final piece: react-router history.location, like window.location, has both pathname and hash components. HashRouter uses the history.location.pathname component to keep track of the client side route after the hash in the browser. The equivalent of this in window.location is stored in window.location.hash, so we will be using this as the value passed to history.replace() instead of window.location.pathname. This confused me for a bit, but makes sense when you think about it.
react-router history also keeps track of its current route with a prepended / instead of a prepended #, since it's just treating it like any normal URL. Before I called history.replace(), I needed to take my window.location.hash, replace the leading hash with a / and then pass that value history.replace()
const slashPath = window.location.hash.replace('#', '/');
history.replace(slashPath);
Whew!

Related

How to create alert from view model

I want to validate file. While file is invalid, i want to refresh my page and inform user that he did not upload proper file. So i have this in my
views/campaign.py
try:
wb = load_workbook(mp_file)
except BadZipfile:
return redirect('campaign_add', client_id)
The only way i know how to do it is add another attribute to client class which will be
is_error(models.BooleanField())
And then change views/campaign to
try:
client.is_error = False
wb = load_workbook(mp_file)
client.save()
except BadZipfile:
client.is_error = True
client.save()
return redirect('campaign_add', client)
And with another attribute i can add in my campaign.html file some kind of if is.error is true i'm adding some kind of windows with information about bad file after reloading page. But is there any way to do it without adding another attribute?
Ok, let's imagine that the answer is a little bit complicated than you've expected.
Modern UI's are not reloading pages just to inform about some errors with user input or upload.
So what is the best user experience here?
User is uploading some file(s) from the page.
You are sending a file via JavaScript to the dedicated API endpoint for this uploading. Let's say /workbook/uploads/. You need to create a handler for this endpoint (view)
Endpoint returns 200 OK with the empty body on success or an error, let's say 400 Bad Request with detailed JSON in the body to show to the user what's wrong.
You're parsing responses in JavaScript and show the user what's wrong
No refreshes are needed. 🙌
But the particular answer will need more code from your implementation. (view, urls, template)

Cypress: How to visit a url of a different origin?

I'm new to cypress and have ran into an issue. I have my base URL set to the domain I want to test, the issue is when I want to test the ability to login on my base url site I need to verify the user on another site, once I click apply on site number 2 the page on my base url reloads and I would then be able to test the rest of the site.
When I try to visit site 2 from my test I get an error
cy.visit() failed because you are attempting to visit a URL that is of
a different origin.
The new URL is considered a different origin because the following
parts of the URL are different:
superdomain
You may only cy.visit() same-origin URLs within a single test.
I read this https://docs.cypress.io/guides/guides/web-security.html#Set-chromeWebSecurity-to-false I've tried setting "chromeWebSecurity": false in cypress.json but I still get the same issue (I'm running in chrome)
Is there something I am missing?
As a temporary but solid work around, I was able to find this script in one of the Cypress Git issue threads (I don't remember where I found it so I can't link back to it)
Add the below to your cypress commands file
Cypress.Commands.add('forceVisit', url => {
cy.window().then(win => {
return win.open(url, '_self');
});
});
and in your tests you can call
cy.forceVisit("www.google.com")
From version 9.6.0 of cypress, you can use cy.origin.
If you want to use it, you must first set the "experimentalSessionAndOrigin" record to true.
{
"experimentalSessionAndOrigin": true
}
And here's how to use it.
cy.origin('www.example.com', () => {
cy.visit('/')
})
cy.origin change the baseurl, so you can link to another external link via cy.visit('/').
You can stub the redirect from login site to base site, and assert the URL that was called.
Based on Cypress tips and tricks here is a custom command to do the stubbing.
The login page may be using one of several methods to redirect, so besides the replace(<new-url>) stub given in the tip I've added href = <new-url> and assign(<new-url>).
Stubbing command
Cypress.Commands.add('stubRedirect', () => {
cy.once('window:before:load', (win) => {
win.__location = { // set up the stub
replace: cy.stub().as('replace'),
assign: cy.stub().as('assign'),
href: null,
}
cy.stub(win.__location, 'href').set(cy.stub().as('href'))
})
cy.intercept('GET', '*.html', (req) => { // catch the page as it loads
req.continue(res => {
res.body = res.body
.replaceAll('window.location.replace', 'window.__location.replace')
.replaceAll('window.location.assign', 'window.__location.assign')
.replaceAll('window.location.href', 'window.__location.href')
})
}).as('index')
})
Test
it('checks that login page redirects to baseUrl', () => {
cy.stubRedirect()
cy.visit(<url-for-verifying-user>)
cy.wait('#index') // waiting for the window load
cy.('button').contains('Apply').click() // trigger the redirect
const alias = '#replace' // or '#assign' or '#href'
// depending on the method used to redirect
// if you don't know which, try each one
cy.get(alias)
.should('have.been.calledOnceWith', <base-url-expected-in-redirect>)
})
You can't!
But, maybe it will be possible soon. See Cypress ticket #944.
Meanwhile you can refer to my lighthearted comment in the same thread where I describe how I cope with the issue while Cypress devs are working on multi-domain support:
For everyone following this, I feel your pain! #944 (comment) really gives hope, so while we're patiently waiting, here's a workaround that I'm using to write multi-domain e2e cypress tests today. Yes, it is horrible, but I hope you will forgive me my sins. Here are the four easy steps:
Given that you can only have one cy.visit() per it, write multiple its.
Yes, your tests now depend on each other. Add cypress-fail-fast to make sure you don't even attempt to run other tests if something failed (your whole describe is a single test now, and it makes sense in this sick alternate reality).
It is very likely that you will need to pass data between your its. Remember, we're already on this crazy “wrong” path, so nothing can stop us naughty people. Just use cy.writeFile() to save your state (whatever you might need), and use cy.readFile() to restore it at the beginning of your next it.
Sue me.
All I care about at this point is that my system has tests. If cypress adds proper support for multiple domains, fantastic! I'll refactor my tests then. Until that happens, I'd have to live with horrible non-retriable tests. Better than not having proper e2e tests, right? Right?
You could set the window.location.href manually which triggers a page load, this works for me:
const url = 'http://localhost:8000';
cy.visit(url);
// second "visit"
cy.window().then(win => win.location.href = url);
You will also need to add "chromeWebSecurity": false to your cypress.json configuration.
Note: setting the window to navigate won't tell cypress to wait for the page load, you need to wait for the page to load yourself, or use timeout on get.

External authentication doesn't work in Datazen

I set the authentication in control panel as below:
and i call the viewer by this code:
$.ajax({url: 'http://192.168.17.31/viewer',
headers: {'thisistheheadername':'thisistheheadername'},
type : 'GET'
});
but still request redirects to login page:
http://192.168.17.31/viewer/login
I'm not totally sure what you're trying to do. I've never seen AJAX used with Datazen like this, although I suppose I can understand how it might work. Of course, it won't be secure, because the browser could always indicate who the user should be--there's no checking.
The top chart in this answer might help you. But beyond that, what tells you it's sending to the login page? Where are you trying to display the results?
The two things I would check on outside of those, though, are:
Your header is not correct, unless you've got a user named "thisistheheadername" as well. You should be passing a username through that value. I don't recall specifically, but it could send you to the login page if it doesn't recognize the username.
Are you sure external authentication is enabled? I would check in the "Configuration" section on the server to ensure it isn't still "default." The core service must be stopped before changes are persisted.

Yii2 skip to other URL route if <controller>/<action> is not found

I have a custom URL route for a controller action i.e:
['pattern'=>'<username>/<post_slug>', 'route'=>'post/view'],
Entering something like localhost/thisismyname/my-user-post would call post/view with the username and post_slug parameters as intended.
The issue with this route is that all my other controller actions no longer work. localhost/post/index would call post/view with the parameters post as username and index as post_slug.
Is there an efficient way to give priority to <controller>/</action> URLs, and if there's no said <controller>/<action> it would pass the parameters as <username>/<post_slug> to the post/view URL route?
I don't want to create a separate route like site/<controller>/<action> or manually add every controller & action as a route into the URL Manager since both are ugly workarounds.
The rule that you specify is very ambigous, so almost any route match it.
You can write a urlRule class and check, is the route <username>/<post_slug> route or not.
As another solution you can place this rule at the very end of your urlManager's rules configuration array.

Oracle APEX - HTML Links Breaks Session and Requires New Login

Ok so here is what is happening:
I have a client that I am building an application for. My client has a flowchart that they would like posted on the front page of their application. Check. My client then wants this flowchart to be set up as an image map so that a user could click one of the boxes in this flowchart and be taken to a report in another part of the application. Check.
All of that is elementary and, in a technical sense, works. The issue is, and it is an issue I have encountered before with APEX, is that every time a user clicks one of these links it takes them to the login screen. It seems that linking directly to a page's URL breaks the session and requires you to login again, even if you are linking from one page in the application to another in the same application.
I have played with all of the authentication settings in a hopes of fixing this and tried to determine what is breaking the session exactly but with no luck.
Has anyone else had this problem and could share their method for fixing it? I really cant have users logging in every time they click a link and I also cannot simply remove the authentication on the pages. Thanks in advance.
You should pass on the session id in your links. If you don't, then apex will see this as a new session. You can tell from the url: take note of the session id in your url when you are on your image map. When you select an application, take another look at the session id part in the url. If they are different, then you are starting a new session each time.
/apex/f?p=190:90:1674713700462259:::::
190 -> application id
90 -> page id
1674713700462259 -> Session id
To pass on the session, it depends where you construct your links.
In PLSQL, you can find it through :SESSION or :APP_SESSION
For example, in a plsql dynamic region: htp.p('the session id is '||:SESSION);
In javascript code you can use $v("pInstance") to retrieve the value dynamically, or use &APP_SESSION. which will have the value substituted at runtime.
Small example:
function printsome(){
var d = $("<div></div>");
d.text('&APP_SESSION. = ' + $v("pInstance"));
$("body").append(d);
};
So you probably just need to alter the construction of your link somewhat to include the session!
I was assuming the binding variables will do the job. But they were helpless.
Best way is to pass the current session id to an item then use the item value in the link.
f?p=&APP_ID.:32:&P31_SESSION.:::P32_CUSTOMER_ID:#CUSTOMER_ID#