I am developing a react app with react router with a server hosting both the react app itself and a REST API. The router is ConnectedRouter from react-router-redux. As I understand, once you have opened the app in your browser, any changes to the URL will not be requested from the server but instead handled client-side in react router, even if you reload the page or write something manually into the address bar.
When first opening the app, the actual HTML, JS and CSS files are loaded from the web server, which is configured to only serve them if the user is authorized. This way, the server can redirect the user to a third-party authentication site, or serve a 401 message if the user is authenticated but not authorized. However, the problem occurs when the user session expires, he logs out or his permissions change so that he's no longer authorized to open the app. Then when the user opens the app on a browser that has previously cached it, it appears to open as normal, but with lots of errors because every API call now fails.
To solve this I would like to, when the API calls fail, tell the user to reload the page so that a request is made to the server to fetch the app (index.html, etc), so that he gets redirected to the third party authentication site. However a regular browser reload (F5) doesn't cut it. The request is swallowed by react-router, and never hits the server. This is also the case with window.location.reload(). A hard reload, i.e. Ctrl+F5 seems to do the trick, at least in chrome, but some users may not understand that. Thus I would like to present the user with a "hard reload" button that temporarily tells react-router to yield url requests, so that they can pass to the server and try to fetch index.html.
So my question is: How can I temporarily and programatically force the client-side app to allow URL requests to go to the server as normal, as opposed to handling them itself in react-router?
Alternatively, if this is not possible, how can I programatically purge the app from the browser's memory, so that it will be forced to request the server again?
Edit:
It seems my assumption that the requests are swallowed by react-router was wrong. It was the service worker in the create-react-app framework. The service worker exposes an unregister function that solved my problem.
As I understand, once you have opened the app in your browser, any changes to the URL will not be requested from the server but instead handled client-side in react router, even if you reload the page or write something manually into the address bar.
This is not true. Upon manual change in address bar (and pressing enter), you will always first hit server router.
It is very likely that your server, no matter which url is hit, will always render your index.html which contains your React app. Something similar to this:
app.use(express.static(path.join(__dirname, 'dist')));
app.get('*', function(req, res) {
res.sendfile('./dist/index.html');
});
Make sure your API requests do not conflict with this. If possible, share some code, so we can review what could go wrong.
Related
I started a Django app and i created the whole authentication layer using Django-Allauth, it already has quite some features such as email confirmation, password reset and two factor authentication. Now i realized that, since my app will be heavily interactive and with a lot of real time features, i'm going to need a Vue SPA to use with Django, so i'm thinking of creating the Vue SPA on the same server and domain and have Django as a Rest API.
Here is my problem: since i already made the whole authentication part using Django templates and Django urls, if i separate the rest of the frontend from the backend, will i have to rewrite everything? Is it possible to have a Vue app and a Django backend app separated on the same domain where authentication is handled by Django templates and all the rest is a Vue app with vue routes and all the other interactions are handled by Django Rest Framework endpoints?
So maybe something like this:
urlpatterns = [
path('accounts/signup/', SignUpView.as_view(), name='signup'), #Django template
path('accounts/login/', LoginView.as_view(), name='login'), #Django template
...
]
And these are the only Django-handled urls where the page is rendered by Django views. Once the user is logged in, they will be redirected to the VueJS app.
My personal opinion, it's not worth it to keep a bunch of server side pages just for sign-up, login, ... Managing both server-side pages and front-end pages in long run is a headache. But if you like that way, here are my suggestions.
For authentication, use Django auth. No matter if it's a server side HTML page or it's an API end-point. Django auth is simple and secure. Don't roll your own auth, don't store tokens in localstorage or so.
Fully separate these 3:
Front-end URLs (i.e. routes stored in Vue)
Back-end page URLs (i.e. HTML pages severd by Django)
Back-end API end-points URLs (i.e. the ones user never see, only Vue uses them under the hood)
They can be on separated domains but it can be just by a path prefix as well. As you yourself suggested in a comment.
Now when user visits some page in BE, it will use server side rendering and every user click is a browser refresh. Until you hit a FE URLs, then your front proxy should redirect user to FE or you'll serve JS files directly from Django. After that every user click is handled inside Vue without a refresh. If user hits a URL prefix that's for BE, then FE needs to do something like document.location = "/server-side/some-page.
BTW few days ago I answered another question that was similar to this, maybe you find the answer or comments there useful.
So in order to log in from the SPA i need to send a csrf token, and in order to get the token i can create a Django view that returns a CSRF token to the user so that it can be used to login. Wouldn't it provide attackers a way to attack my server (stackoverflow.com/questions/43567052/…)
My suggestion is to turn CSRF protection off and instead make session cookie samesite=Lax (I think that's default in newer versions of Django). All major browsers support this and it prevents CSRF.
Otherwise you can read token from another API or from cookie like here
So on production i will use Nginx to have the Vue app and the Django backend app on the same server and domain, but on development how can i do that? If i run the two apps on different terminals, won't django consider the Vue app to be in a different server?
It can't understand what server it is. The only thing you should care is the domain of the cookie. It should be set on your BE domain. When running on local both FE and BE are running on domain "localhost" so there should be no issue.
I'm trying to user firebase authentication (saml) within my Electron app. I have read that Electron isn't "officially supported", however I've also encountered some threads that indicate people have gotten it to work. My issue is when using firebase.auth().signInWithRedirect (or signInWithPopup), I get the error:
code: "auth/operation-not-supported-in-this-environment"
message: "This operation is not supported in the environment this application
is running on. "location.protocol" must be http, https or chrome-extension and
web storage must be enabled."
Google turns up this tips and tricks article which suggests just using a hosted URL:
Third, OAuth requests need to have a verifiable origin, which can be whitelisted in the Firebase Console. This requires the current Electron browser window to use the http or https protocol, and not file. That means the current window contents should be loaded with browserWindow.loadURL and not browserWindow.loadFile, and the url must be an http or https url, not a file:// url. This generally requires a hosted page somewhere that gets wrapped into the Electron window.
This will probably work, as just running the app locally on the angular dev server works by simply using:
mainWindow.loadURL('http://localhost:4200');
I don't want to do this because having a hosted page lessens much of the appeal of having a native app.
I could make this work in by just having a local Node/Express instance serve the app locally using get-port to find a free port to run the app there. I haven't really seen any examples showing this. There are a few Stackoverflow questions on getting it to work, but nothing that makes me think it is an acceptable production-level solution.
Is a local Express server an acceptable way to circumvent this error, or is there currently a better solution?
Note: The package recommended in this answer is not published by Firebase and anyway doesn't look like it supports SAML auth.
I had to do something very similar but with Auth0 instead of Firebase.
What I had to do was make two Browser Windows, where the first one is strictly an authentication Browser Window while the second one is the actual main App Browser Window.
Auth0 has the same restrictions of whitelisting only http/s protocols, so in the authentication Browser window, I load the Auth0 sign in URL via localhost (it’s a hosted URL in Auth0 itself). I then take the credentials (access token and refresh token) from that redirect/Oauth process and store them locally for the time being (use Keytar, SafeStorage, FS modules, whatever; I chose Keytar).
Then my authentication Browser Window closes itself and launches the main, app Browser Window.
From here onward, I have to reference my stored access token and refresh tokens from my storage location and use those in all other calls.
So, for instance, I have some API library modules I made for myself that use Axios. So I bring the saved access token into all of my Axios calls. Works like a charm.
So, in summary: I had to use two different Browser Windows and persist credentials locally for later use in the main Browser Window.
Check out Auth0’s guide on using Electron for authentication; I think you’ll find that many concepts carry over for Firebase and other providers.
i've successfuly implemented firebase email ver in my app, it is more feasible to load .html file rather than load a page from url as users can exploit the the url by sending fake data. but with oauth it is not possible,firebase create webapp you can create a html file it should contain api keys, host url etc and host it locally in android file://android_assets/filename.html this works fine even offline i dont know properly about
electron
I'm trying to setup a protected route (homepage in my case) with React. It works fine on the client. If not authenticated by server, it reroutes to login page. But technically, unauthenticated users can still check out the static content on the protected route (though of course the api calls to server are safe), just by either sifting through code, or by setting state in dev tools. I don't like this.
TLDR question: How can I make sure even the static content in protected routes are not seen by unauthenticated users?
I understand that authentication has to move from client to the server. But what is the proper way to do this with React/React Router?
My ideas:
-Serve an unauthenticated react app/index.html for just Login. When authenticated, serve another app for user content/pages.
-Maybe it's possible to do some component lazy loading from the server that also checks authentication on the request?
My context: create-react-app, express/node backend, using okta auth.
Thank you.
There are a couple of ways around this using a variety of methods.
First is to server-side render everything with a framework like Next.js. This framework is used by a ton of large enterprise companies because of the search engine friendliness of SSR pages. In your scenario though, it would solve your problem of showing content only when someone is authorized.
However, in most React.js apps, your data is coming from a data source such as MongoDB that is hidden behind your backend. The only code/information an unauthorized user would be able to see in your JS is the general layout of pages.
You can make hoc which will wrap your protected component and check if he is authenticated by a server. If not redirect him to homepage or somewhere else.
I'm using JWT authentication in my node site with passport and am not sure I am understanding the concepts.
Say I'm an authenticated user with my token saved in local storage. Say I then navigate to a /user page, which will display data about my user. Normally, I would check if the user is logged in, and if they are not, they would get redirected to a login page. But in this case, since I can't send the authentication token from a page request, I have to load my /user page, then the page makes a request for user data, and if the data is not found, I redirect them to the login page via javascript.
Am I correct in how I would handle this? It seems like a bad user experience, having to wait and redirect twice. Is there a way around this? Is JWT just not what I'm looking for my implementation?
If you want to render the page server side, you should set the JWT as a cookie instead of using local storage. You would be able to catch, verify and use it when a user request a page.
But I have to say that modern web applications use client side rendering. So there's no need to store the JWT as a cookie. When requesting a page, you will recieve only the static assets and you will get data by queryng an API that could response with 401 (Invalid or Expired Or Missing Token).
It seems you were using server side rendering before. Now you want to use JWT and you have read somewhere that a common practice is to store it in LocalStorage (that's true). Now you are dealing with a server side rendered application architecture while tryng to mix client side rndered applications concepts.
You are not doing huge errors, but you should consider to fully render your application server or client( I suggest ) side. For the second option, google "Build single page application"
I'm creating an AngularJS application that uses the JWT token for authentication. The token is being passed using the AngularJS interceptor as shown below.
'request': function(config)
{
if (store.get('jwt_token') != null)
{
config.headers['x-access-token'] = store.get('jwt_token');
}
else
{
config.headers['x-access-token'] = '';
}
return config;
}
Whenever I'm accessing any /restricted pages, everything is working fine. The issue is when I'm going to the /restricted page by directly typing the URL in the address bar (or refreshing the page), the AngularJS gets circumvented, and hence, the Interceptors don't intercept the request, and the token is not passed.
I've been searching for a while, I found some solutions like responding with a piece of code that loads the AngularJS then makes a redirect from there. However, I'm looking for a simpler/neater approach if possible as I might be missing something here.
I can detect if the request came from AngularJS or not by checking for the x-access-token since I'm always passing it (empty value if user is not authenticated).
Solution
Alex's answer is pointing to the exact problem, thanks Alex.
I finally figured it out yesterday. The solution I went with was to make sure all the requests come from AngularJS, I have a list of the restricted pages, if any of them is requested, I'm calling a function to verify and validate the JWT token on server side, if it's valid, proceed, otherwise, go to login page. The key thing is to ensure that ALL requests should go to the index.html to make sure AngularJS is handling the routing.
This link helped me greatly to solve this issue.
http://arthur.gonigberg.com/2013/06/29/angularjs-role-based-auth/
It sounds as if there's a confusion between Angular's router and server endpoints.
You are presumably triggering the $http configuration while navigating through the application, using URL's tracked by Angular's router (which works fine), whereas the /restricted URLs are server URLs.
Therefore, when you ask for anything /restricted outside of the Angular app (in the browser), it is sending the request straight to the server, and not via the Angular router or application.
Basically, you need to create routes in your Angular app that are within the restricted context, that when initialized, run the $http configuration. You should never be calling server end-points directly in the address bar, except for index.html (/).
I'd suggest having an Angular route called /restricted-area so that when a user hits it, it will always use the $http methods you have, by being a dedicated Angular route, calling the server's /restricted endpoint internally, via a controller.
I had asked the similar question 2 months ago. What I have understood is, normally before javascript frontend frameworks how the http request were served was:
We type a url in address bar.
The browser sends the request.
The server gets the request.
serves the necessary html, js and css files.
browser renders it.
But as the recent shift to various javascript frontend frameworks and use of RESTful api.s has begun, the request needs to have authorization header. Thus in many of the single page web apps with javascript frameworks like angularjs,
the initial request for '/' router is sent
the server serves the web application to your browser
all the further routing in the web application is done within your front end application, hence the "#" after your url.
Requests are made by the application to fetch, update, delete or post from your application through angular js.
So when you make request from angular application. Your request is intercepted from angular application and intercepted by your interceptor. However when you enter the url from your address bar, the browser sends request directly to server, because at that point of the request, the browser has not loaded your angular web application.
What I suggest is you set html5mode to false and put a # before your route.
like localhost/#/restricted and do the routing from your route provider. When there is # before your route, the request for / goes to server, your application loads, and make some controller in /restricted from your application make http request to desired server end point. In this way the request is sent through your application and will be intercepted by your interceptor. The refresh and directly typing the address in address bar works as well.
I assume if you access the page /restricted your Angular app will start up correctly and it is just a login problem.
If you don't have a token then you have to redirect to the login page. You could show the login as a modal overlay then you don't have to switch the page. The interceptor will monitor the response result for status 401. In that case the interceptor will show the login. After the successful login the interceptor will execute the origin request - with the token.
You can find a working example in the angular app
just pass this token in cookies, not in header.