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.
Related
I load user profile informations from instagram by the basic api in a local test app.
So this actually works like written in the documentations.
I used Postman to get along the authentication and token stuff.
The order to access the graph api, to query media fields would be like:
Get access code (from authentication window redirect url)
Get access token (from acces_token endpoint)
Get media data (from graph api by access_token)
I´m using UI5 js framework with UI5 Tooling.
I get a response with data in step 3 from ajax call, but first i have to execute step 1 and step 2 manually.
But i want to do all this authentication-mechanism directly in my app. Including opening this authentication window and acessing the code from the redirect url.
When i do an ajax call in step 1, i get an CORS error of course, because step 1 doesnt respond with the corresponding CORS header (like step 3 does).
Well, anyways i most likely could handle this by a proxy, but whats about the production environment? I dont get how this approach should ever work in a real app. Even when the CORS problems are handled by aproxy in dev environment, it should be still there when a real user uses the app.
So the question is: How can i access or implement this authentication window (on a diffrent origin) in my app? I have seen other apps doing this permission window, but i have no clue how to implement it in a web app.
By clicking "Allow" you get redirected to he redirect_url with the access code
How can i get the access code directly in my app, avoiding CORS in production
I don't have a definite answer, but regarding your CORS issue: It seems like instagram added a CORS policy somewhere last year (see this other question on SO). This means that you would indeed have to build a proxy.
Basically you need something that accepts all the endpoints that the original API accepts, forwards them to instagram, reads the response, and returns the response to the client (aka browser). But your proxy will then not add the CORS headers. Or even better, you add your own CORS headers (assuming your proxy and your app will run on the same server) and no one else will be able to use your proxy from their web app.
Unfortunately I don't know about this authentication window. Maybe this is just a fancy way to hide that they are running a proxy behind the scenes as well? You should be able to see in the network tab where the insta data is coming from after you login. I would guess not directly from their graph API.
Let's imagine a client opens your nuxt.js website on the index page. From there, they authenticate (you used #nuxtjs/auth-next for that purpose). Then they move to a secure page that only authenticated users can see. This secure page is a .vue file in your "pages" folder with middleware: ["auth"].
Now, how is this page really secure ?
I mean, couldn't a malicious user temper with the page and access it without being authenticated anyway ? Because the "security" in this scenario is only implemented on the client side right ? [Edit]
Your application being an SPA at the end, if you want to bypass a middleware with it's security checkup, you could disable the JS on the page. But then, since no content is generated directly, you won't see anything because it's not here (as a static file).
If your app is isomorphic (basically has a ssr: true), the auth module will still disable the access to those pages (you can double check).
At the end, the critical info is received when:
you do have a valid JWT token (after some login)
you submit an HTTP query to the backend
the backend acknowledges it and the token is valid
the backend gives you the sensitive info via an HTTP response
At the end, your client side code doesn't need to be secure. If somebody somehow hacks your client side state and reaches the sensitive page, he will still not have a valid JWT token since the verification still happens on the backend.
The one that can be generated only when sending the proper credentials to the backend and having the backend validating those.
Now, how is this page really secure ?
The protected content is served from a request if a valid access token has been provided by the client.
The protected content is provided at runtime.
Because the "security" in this scenario is only implemented on the client side right ?
The security is not only implemented on the client side.
The premise is: The access token has been obtained securely through an authentication flow with an auth-server.
I recommend to read more about auth flows if this sounds unclear.
Auth0 has some good documentation on different flows.
https://auth0.com/docs/authorization/flows
Then, what is the best way to show a complex page to authenticated users only ?
The content is provided at run-time. Server-side or client-side.
There are some setup guides here for Nuxt.
Here is the first (Auth0) I found from the list.
https://auth.nuxtjs.org/providers/auth0
I don't know how updated those guides are, but the auth service providers tend to have updated guides themselves.
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 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.
We are having two applications. One is written running on salesforce (App A and other app (App B) is using angularJS for UI.
When a user performs a certain actions in App A, App B has to be opened in a new tab. In this case, certain parameters have to be passed to App B from App A.
As App A and App B are running in different domain we can't use cookies to share the data.
So we wanted to pass it as a http header. Is it possible to read the http header in angular JS?
Please note that this different from AngularJS - accessing http headers. That was related when we access a http from angular JS. But we want to read the http headers passed to our angularJS application.
How can we read the HTTP headers sent to an angular JS application?
Or is there any better approach to solve this issue?
You can access headers from $http or from $resource but depends on what you are trying to do. If passing the state/data then it might not be possible.
pass as query string in the App B URL and open in new window/tab. Login information might not persist in second domain if you are not using Auth2/Auth domain authorization method and not sending data using get method with request data when opening in new tab or window.
You need the assistance from server side, you don't have the access to page headers in JS, and it isn't Angular issue.
You can try cross-domain localStorage with this package. It also has Angular module bundled.