-
Notifications
You must be signed in to change notification settings - Fork 117
A supposed way to auth requests from SSR to Feathers API #469
Comments
What Framework are you using? This is something worth gathering a collection of examples and demos. You'll for sure need to enable cookies. And make sure you're using the pre release of the generator. The new auth plugins help with implementation of SSR quite a bit thanks to the cookie support. I'm on my phone or I'd help more right now. |
My app consists of a browser client, SSR and API which run in separate processes. So the requests can go in two ways:
There is a good examples of how it is expected to do auth when client talks to API or SSR and API are combined into a single process (like the chat example), but separating these servers is a better practive for production and there is no official examples or info in the docs about it, as i can see. |
The setup is much simpler when the API and SSR server are on the same machine, but I agree that the ideal setup is to keep them separate. The main problem to solve is the cookie. With One problem to consider is the disparity between the transports. The feathers-socketio plugin is, of course, using socket.io, which has to be statefully authenticated on the server. In order to receive authenticated data, you'd have to wait for the socket to connect, then you'd have to make a request to Now back to the cookie. When SSR and API are on the same server, the cookie for the API server will automatically work for the SSR server. This is because browsers send cookies automatically based on the domain. So when you authenticate with the API server, the browser gets the API server's cookie, and all requests to that domain, including SSR requests, will receive the cookie. This makes the cookie available on the SSR server, which probably allows the Putting the SSR server on a different domain, however, gets rid of the ability to automatically use the cookie, because the browser won't send a cookie for a different domain. In order to make this scenario work, the SSR server has to serve as an auth proxy. Here's the basic workflow:
Hopefully this gives you what you need to get going. |
Here is how i got it working. On every request SSR create an API adaptor before routing:
APIClient constructor gets token from cookie an sets it using the set('accessToken', token) method, provided by feathers-authentication-client plugin, this method is not mentioned in docs (at least i do not see it), but it's working:
So, here is a page loading flow i've got:
This APIClient should work in the browser, but it is the next step to debug it. Is it is a supposed way to do it? If so, it would be great to have such example or docs on setting accessToken, i can provide my if you consider it as a good idea. |
Perfect. That's exactly equivalent to what I described, but even works with the 0.7 version of feathers-authentication, I imagine. You've pointed out a very important oversight on my part with the docs, though. It's not pointed out well in the API docs that the application docs apply to both the server and the client, minus the express part. Oh, and the client is very lightweight, so this will not be a concern. It's basically a combination of the application code with the mocked out express object, which are both tiny. The size of any Ajax library would probably be larger. So no concern, in general. I think this would be valuable information to share with the community. Would you like to write a blog post? We could link to it in the documentation. Or, if you prefer, you can put together a nice example and I could write an article about it. I think we need two articles, really. One for SSR on the same machine/domain, and another for SSR on separate domains. |
Ok, thank you! I think i provide it as a one more guide section or a "Built with Feathers" example when i'll debug it. Authenticating With Feathers Client section shows how to obtain and use token on the client and how to build a server that create tokens. It would be great if there were an example of how to obtain and use token when the client is another node js server (SSR). Universal feathers section says that feathers-authentication-client supports "Token authentication (JWT)", but there is absolutely no info on how to do it (i mean how to make authorized requests from node js if you have a token). And it would be very helpful to see an example of access to a service that requires authentication in Universal Feathers for Node JS example, it will save few evenings to everyone who starts using the framework for production apps. Hope my feedback will be useful) |
I created this issue, yesterday, to plan out the guides we need. I'll be working most of those topics into these new guides. The |
@NikitaVlaznev, if you feel like you've figured this out, please feel free to close this. I've got work started for documenting this, already. |
@marshallswain The |
@bertho-zero can you explain what you mean by "export the token"? |
*Extract from request, sorry.. To then easily perform: |
@bertho-zero Want to create a separate issue for that? |
@bertho-zero what's the goal? You want a utility to extract the JWT from a cookie? |
In my export function createApp(req) {
if (req === 'rest') {
return configureApp(rest(host('/api')).superagent(superagent));
}
if (__SERVER__ && req) {
const app = configureApp(rest(host('/api')).superagent(superagent, {
headers: {
Cookie: req.get('cookie'),
authorization: req.header('authorization')
}
}));
const accessToken = req.header('authorization') || (req.cookies && req.cookies['feathers-jwt']);
app.set('accessToken', accessToken);
return app;
}
return configureApp(socketio(socket));
} In my import { createApp } from 'app';
const app = createApp();
const restApp = createApp('rest');
// ...
const renderRouter = props => <ReduxAsyncConnect
{...props}
helpers={{ client, app, restApp }}
filter={item => !item.deferred}
render={applyRouterMiddleware(useScroll())}
/>;
const render = routes => {
match({ history, routes }, (error, redirectLocation, renderProps) => {
ReactDOM.render(
<HotEnabler>
<Provider store={store} app={app} restApp={restApp} key="provider">
<Router {...renderProps} render={renderRouter} history={history}>
{routes}
</Router>
</Provider>
</HotEnabler>,
dest
);
});
};
render(getRoutes(store));
// ... And in my import { createApp } from 'app';
// ...
app.use((req, res) => {
const client = new ApiClient(req);
const clientApp = createApp(req);
const restApp = clientApp;
const memoryHistory = createHistory(req.originalUrl);
const store = createStore(memoryHistory, { client, app: clientApp, restApp });
const history = syncHistoryWithStore(memoryHistory, store);
function hydrateOnClient() {
res.send(`<!doctype html>
${ReactDOM.renderToString(<Html assets={webpackIsomorphicTools.assets()} store={store} />)}`);
}
if (__DISABLE_SSR__) {
return hydrateOnClient();
}
match({
history,
routes: getRoutes(store),
location: req.originalUrl
}, (error, redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search);
} else if (error) {
console.error('ROUTER ERROR:', pretty.render(error));
res.status(500);
hydrateOnClient();
} else if (renderProps) {
loadOnServer({ ...renderProps, store, helpers: { client, app: clientApp, restApp } }).then(() => {
const component = (
<Provider store={store} app={app} restApp={restApp} key="provider">
<ReduxAsyncConnect {...renderProps} />
</Provider>
);
res.status(200);
global.navigator = { userAgent: req.headers['user-agent'] };
res.send(`<!doctype html>
${ReactDOM.renderToString(
<Html assets={webpackIsomorphicTools.assets()} component={component} store={store} />
)}`);
}).catch(mountError => {
console.error('MOUNT ERROR:', pretty.render(mountError));
res.status(500);
hydrateOnClient();
});
} else {
res.status(404).send('Not found');
}
});
});
// ... The main problem is in the file const accessToken = req.header('authorization') || (req.cookies && req.cookies['feathers-jwt']); // <- this line
app.set('accessToken', accessToken); |
@marshallswain You mentioned
Is cookie support enabled by just having the configuration set up properly, and then using the feathers client rest? So these two things: config
client
Now, If I did not have the client part, but a login form instead, that would not work, right? Because the magic that sets the cookie is in the feather client rest configuration as it authenticates to the server, then the server can set the cookie as it authenticates, or the client can set it (or I can manually, since I have the cookie).
I just tried the form and I have the express part to extract the token from the cookie and put it in the header, but that never gets fired when the login route is hit. So when the login success redirect happens, I get 401 not authorized. Just making sure I understand this. Thanks for reading! |
@snewell92 Can you please list out the full workflow of what you're trying to do? I don't quite grasp it. |
This repo is a good place to see where I'm going. I'll explain. So I'm doing more or less a normal web application / website. Users visit the landing page, see a login page, login, and then are taken to a series of pages that they can do stuff with. Normal normal. So, for the flow of authentication I have it set up like so: I have my After the authentication service is registered I set up the auth create/remove hooks, which is just like the docs. So nothing new here. After the authentication is set up I set up mysql/users service. All seems well. At this point I tested using feathers client and postman - both work as rest clients and receive back jwt token - yay! Now all my services are configured, so I move on to make my routes. In the repo this is the file: routes.js. It sets up cookie extraction, validated routes, and the login route (which I don't believe I can use). That So now, on my landing page, I include the
Once the user has clicked the login button or pressed enter I do this:
Unfortunately, even manually setting the cookie client-side doesn't seem to work. I'm not sure what to do. I suspect my To be clear - I am receiving the jwt token in Route validation that I'm talking about: (can be seen in context in routes.js)
|
I didn't even have cookie-parser... so I fixed that and now I get the cookie and get the token... But I still get the same error, even with the cookie being parsed. I don't think the header is being set right with this code...
Or even
I'll need to figure that out. But I think the core of this issue is that this whole process could be solved with @NikitaVlaznev 's auth plugin. But maybe this should be embedded into the authentication service? Why isn't the header being set by the authentication service? |
@snewell92 Did you enable the cookie setting for feathers-authentication? It will automatically create a cookie on login. I'm sorry, but I still haven't been able to figure out what you're trying to accomplish. Are you saying that you have login working and now you're trying to SSR a page? I need a high level overview of what you want to happen. |
SSR => server-side-render? When users get logged in, the application should remember that they're logged in. (like how traditional websites would use a session cookie or something - but instead of that use the jwt token). I have the following cookie settings for local development
It's in the Sorry, I'm not being clear enough :\ I'm not doing a SPA. I'm doing a multi-page application, first page is login page. Redirects to new pages. I get the token on that first page, but on redirects or subsequent user navigation, feathers doesn't think the user is authenticated. Hopefully I can explain better this time: I'm making a full stack web application with feathers on the backend, a 'shell' that is SSR'd (with vue-express), and with angular to compose pieces together on the main page. The only exception being the login page (no SSR, no angular). The shell is just the top main bar, and the side bar - and the main angular entry point that angular bootstraps itself onto. As far as users are concerned current flow is this: Users hit the login page first. Simple login form with username/password. Upon pressing enter for clicking the login button, user is authenticated, we get back a token, then the page redirects to a route that requires an authenticated user. All subsequent links/routes require authentication. Problem is that bolded part. I haven't gotten that working yet. From what I gather feathers totally supports this, since it's supposed to set the Authorization header, and if cookies are enabled set the cookie. I don't know what I've done wrong :\ I'm going to start a fresh project and see if I can duplicate this thing. What I am wanting to do is seperate from my Vuejs SSR + angular. I can just set up basic static site with a couple of routes that use default .ejs views. I'll have a landing page that is a login form which, on success, redirects to a route that requires authorization. I'll add a new comment with a more minimal/repeatable example - or report that I fixed it (hopefully!) thanks for reading. <3<3<3 |
@marshallswain is the code @NikitaVlaznev wrote here necessary if I'm doing authentication via an express route? (ie not using the feathers client?)
And pass it along to |
There is now a recipe showing how to use Feathers authentication with Express middleware (including server side rendering) at https://docs.feathersjs.com/guides/auth/recipe.express-middleware.html |
@daffl, please correct me if i'm wrong. As i can see the application architecture in the recipe you have mentioned differs from the one in this issue starter.
'chat' and 'messages' service are on the same app instance. This Issue was started to clarify how to perform authorization when there is a 3 separate processes: Browser, SSR, API. This architecture is required when SSR is not the only one consumer for the API, there is also some mobile applications and integrated partners. |
The more performant way would be to share the |
It is not a good idea for a big projects, there are a lot of reasons for separating SSR and API, e.g.:
Definitely there will be one more HTTP request, but using feathers-batch it will be the only one, and in most cases it will not be very expensive, because SSR and API usually have LAN connection. |
You have a good docs and examples but it lacks any info on how it is supposed to authorize requests from SSR (Server Side Renderer) to Feathers API.
Is it ok to instantiate feathers-client app for every request? Would not it be to heavy?
There is an example of how to call feathers API from server side:
But what if the messages service will require authenticated user?
Should i just manually get token from SSR's req and add it somehow to api instance or api.service call?
Taking in mind the asynchronous nature of node it seems that durable way here is to call client() inside the app.get '/messages' handler, is it a supposed way?
It is also unclear does one of the main Feathers boilerplate examples have durable SSR authentication, i've described it here.
The text was updated successfully, but these errors were encountered: