Is there a way to "safely" store a bearer token in cookies (server side from an Express server running a Next.js app) and then provide it as part of the header so that it's included with every Apollo request? The Apollo team has an example using localStorage, but nothing about grabbing it from a cookie vs localStorage to set in the header. I'm looking into this in order to mitigate XSS. Is there a way to safely provide a token to this code without exposing the token in the browser? This has seemingly been covered in parts in multiple tutorials, but I can't seem to find any definitive code based on this example.
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
const httpLink = createHttpLink({
uri: '/graphql',
});
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
});
I have an Oauth2 server that is expecting a bearer token sent from the header in the form of authorization: 'Bearer ****'. I'm just looking for the most secure way to do this, and feel like im getting conflicting information from the majority of tutorials.
Making my comments into an answer since it helped you figure things out:
Browser Javascript cannot access a server-only cookie at all. So, if the server is putting the token in a server-only cookie, then you can't access that from browser Javascript. The server could make it just a regular cookie and then you could get to it from browser Javascript. Or, if the client isn't actually in a browser, it could get the cookie.
Is there a way to safely provide a token to this code without exposing the token in the browser?.
If this code is in a browser (which it looks like it is because it's accessing localStorage), then "no", there is no way to let this code have access to the token, but not let other code in the browser have access to it. Anything you access with browser Javascript is accessible to the world. Usually, you maintain security by having your server keep track of auth tokens and having your server access the privileged server on behalf of the client and just send results to the client.
What I'm asking is. Is there a way to attach the token cookie via node.js so that it gets attached to all Apollo requests without adding it via the client.
Are Apollo requests being made direct from the client or only from your server? Sorry, but I don't understand your architecture here. If only from the server, then you can create a client session on the server (see express-session) and you can store things on the server in that session that belong to a specific client and you can then retrieve those things on future requests.
Related
I am currently building a web app based on a turborepo (monorepo) in which I want to use Discord OAuth login with next-auth. Therefore I have two modules web and api, where api is my express backend with discord.js. The web app is basically a dashboard for a Discord bot.
I figured that next-auth only provides client side authentication. So my question is how can I validate the OAuth session from the client side in the best manner?
My middleware for express currently looks like this:
function throwUnauthorized(res: Response) {
res.status(401).json({ code: 401, message: 'Unauthorized' });
}
export async function isAuthorized(req: Request, res: Response, next: NextFunction) {
try {
const authorization = req.headers.authorization;
if (!authorization) {
return throwUnauthorized(res);
}
// validate token with Discord API
const { data } = await axios.get('https://discord.com/api/oauth2/#me', {
headers: { Authorization: authorization },
});
// protect against token reuse
if (!data || data.application.id !== process.env.TC_DISCORD_CLIENT_ID) {
return throwUnauthorized(res);
}
// map to database user
let user = await User.findOne({ id: data.user.id });
user ??= await User.create({ id: data.user.id });
data.user.permissions = user.permissions;
req.user = data.user;
next();
} catch (error) {
return throwUnauthorized(res);
}
}
In this approach the Discord OAuth Token would be send via the Authorization header and checked before each request that requires Authorization. Which leads to my problem: The token needs to be validated again causing multiple request to Discord API.
Is there a better way to handle this? Because I need to map Discord user profiles to database profiles. I read that you could try decode the jwt session token from next-auth, but this did not work when I tested it.
Maybe there is a whole different project structure suggested for my project. But I thought I should separate the api and web-app since I would have needed a custom express server because it includes the Discord bot and Prometheus logging functions. I am open for suggestions and your thoughts!
I need to secure a web page with a token stored in a cookie or url param. All examples I can find for using forwardAuth middleware seems to be for securing an API, as it's easy to supply headers in an API request. Sending custom headers isn't an option w/ the browser, so I need to used cookies.
I would like to have the auth token passed in through a query string arg, eg ?token=ABCDEFG, then stored in a cookie for future requests. Here's what the workflow looks like:
I've tried experimenting with forwardAuth to see how I can do this. The auth endpoint reads the Authorization header, but I need something that reads the cookie in the request and transforms that to an Authorization header.
Is there any way this can be done with Traefik?
It looks like the answer is yes. Originally I had thought traefik wouldn't forward cookies, but it does in fact appear to forward cookies.
I ended up creating a "sidecar" auth container on the same host as traefik so that auth requests would be faster.
The auth function looks like this (node/express):
app.get('/auth', (req, res) => {
logger.info('CHECKING AUTH');
const url = new URL(`${req.headers['x-forwarded-proto']}://` +
`${req.headers['x-forwarded-host']}` +
`${req.headers['x-forwarded-uri']}`);
const urlAuthToken = url.searchParams.get('token');
if (urlAuthToken) {
url.searchParams.delete('token');
const domain = BASE_DOMAIN;
const sameSite = false;
const secure = url.protocol === 'https:';
return res
.cookie('auth-token', urlAuthToken, {domain, sameSite, secure})
.redirect(url.toString());
}
// Simulate credentials check
if (req.cookies['auth-token'] === 'my-little-secret') {
return res.status(200).send();
}
return res.status(401).send('<h1>401: Unauthorized</h1>');
});
I'm trying to build SSR application using NextJS and apollo-client on the frontend, and graphql with express using (graphQL Yoga) on the backend.
I came from client side rendering background and things there are simpler than SSR when it comes to authentication, in regular client side rendering my approach to authenticate user was like:
1- once the user login after server validation, sign a JWT with current user data, then send it to the client side, and save it in localstorage or cookies, etc...
2- implement a loadUser() function and call it in the (root) App component's useEffect hook to load the user in every component (page) if the JWT in localstorage is valid.
3- if the JWT isn't there or is invalid just return user as null and redirect to login page.
so in Next.js i know we can't access localstorage cause it works server side, so we just save the token in a cookie, and the approach i implemented is painful and i was wondering if there is an pimplier way, my approach is like:
1- once the user login he calls the login mutation which sets a cookie in the req header, and return a user and any data i want.
2- in each page that requires authentication i need to get the token from the cookie to send it back in the header and i did that in getInitialProps() or getServerSideProps() cause both runs server side and have access to the request cookies in the header like so:
export const getServerSideProps = async ctx => {
const apolloClient = initializeApollo();
// get the cookies from the headers in the request object
const token = ctx.req.headers.cookie ? ctx.req.headers.cookie : null;
return {
props: {
initialApolloState: apolloClient.cache.extract(),
token: token
}
};
};
now i have access to the token in the page props and can send the token back with the req header with my apollo client like so:
let getUserQuery = await apolloClient.query({
query: GET_USER_QUERY,
variables: { id: ctx.params.id },
context: { headers: { token: token } }
});
now i have access to the token in the server side request like req.headers.token
what i wanna achieve:
1- is there an easier way to implement loadUser() that loads the user with every page render that i can implement in next.js custom _app , i found this answer but it doesn't return auth object or user in all components as he mentioned in his answer.
2- i read that if i set cookies httpOnly and credentials: "include" i have access to cookie in every request, but it seems that it doesn't work with apollo client, that would be awesome if there is an alternative approach.
3- there is apollo-link-context provided by apollo team where i can send a token or any value in every request's header using setContext() like so:
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = localStorage.getItem('token');
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
});
but since i don't have access to localstorage i can't implement it cause next runs server side, so if anyone has an implementation for this please consider sharing.
PS. i made this thread after searching and reading for like 1 week and it's my last resort to ask you guys, and thanks in advance.
get token by store
store.getState()..path.to.your.token
the problem is that the token doesn't completely update when the blind changes and I'm looking for a solution.
I added authentication for my Express API following this guide and after testing my secret-routes everything seems to work properly. Now my question is how can this be used in an Ember app login page. After receiving the secret token after a successful login how does the browser know you are signed in. How would one log out? How does the ember application know who is signed in? Is there any thing in particular security wise that I should be at tentative to while working on this?
You should use addons to handle most of the heavy lifting for you.
ember-simple-auth-token has setup directions that have you create a login route which will take a username / password and send it to your server for validation. The token response will then be available in your app until the user logs out.
The example looks like
import Controller from '#ember/controller';
import { inject } from '#ember/service';
export default Controller.extend({
session: inject('session'),
actions: {
authenticate: function() {
const credentials = this.getProperties('username', 'password');
const authenticator = 'authenticator:token'; // or 'authenticator:jwt'
this.get('session').authenticate(authenticator, credentials);
}
}
});
You also create the logout route which handles logging out of your app as well as sending any logout request to the server.
If possible you should align your server to the defaults, but you can configure nearly everything.
Authentication Options
ENV['ember-simple-auth-token'] = {
tokenDataPropertyName: 'tokenData'; // Key in session to store token data
refreshAccessTokens: true, // Enables access token refreshing
tokenExpirationInvalidateSession: true, // Enables session invalidation on token expiration
serverTokenRefreshEndpoint: '/api/token-refresh/', // Server endpoint to send refresh request
refreshTokenPropertyName: 'refresh_token', // Key in server response that contains the refresh token
tokenExpireName: 'exp', // Field containing token expiration
refreshLeeway: 0 // Amount of time to send refresh request before token expiration
};
We've been very happy with this addon in production for 3 years and I'd highly recommend it.
I have a working oauth2 authentication process where I get an access token (eg from facebook) using ember simple auth, send it to the back end which calls fb.me() and then uses JWT to create a token. This token is then sent back to the ember app, which then has to send it with every server request, include those requests made by ember-data.
I also need to have this token available after a browser reload.
I have tried many options, where I set a property 'authToken' on the session - I believe that this uses local storage to persist the authenticated session.
But I always seem to have trouble with coordinating the retrieval of this token - either I don't have access to the session, or the token is no longer on the session, or I can't change the ember data headers.
Does anyone have a working simple example of how this can be done - I think it should be easy, but I'm obviously missing something!
Thanks.
Update
The only thing I've been able to get working is to use torii as shown below, but the session content is still lost on refresh - I can see its still authenticated, but its lost the token I set here. So I'm still looking for a real solution.
authenticateWithGooglePlus: function () {
var self = this;
this.get('session').authenticate('simple-auth-authenticator:torii', 'google-oauth2')
.then(function () {
resolveCodeToToken(self.get('session'), self);
});
}
resolveCodeToToken gets the bearer token from the server, sets it on the session and then transitions to the protected page:
function resolveCodeToToken(session, route) {
var authCode = session.content.authorizationCode;
var type = session.content.provider.split('-')[0];
$.ajax({
url: 'http://localhost:4200/api/1/user/auth/' + type,
data: {authCode: authCode}
}).done(function (response) {
// todo handle invalid cases - where user is denied access eg user is disabled
session.set('authToken', response.token);
route.transitionTo('activity', moment().format('DDMMYYYY'));
});
}
And I have a custom authorizer for putting the token (stored in the session) on every request:
import Base from 'simple-auth/authorizers/base';
export default Base.extend({
authorize: function(jqXHR, requestOptions) {
var accessToken = this.get('session.content.authToken');
if (this.get('session.isAuthenticated') && !Ember.isEmpty(accessToken)) {
jqXHR.setRequestHeader('Authorization', accessToken);
}
}
});
I'm not sure why this.get('session.content.authToken') would be undefined after a refresh, I thought by default the session was persisted in local storage. The fact that it is authenticated is persisted, but thats useless without the token since the server will reject calls to protected endpoints.
You'd want to implement your own custom authenticator that first gets a token from Facebook and then sends that to your own server to exchange it for a token for your app. Once you have that you get authorization of ember-data requests as well as session persistence etc. for free.
Have a look at this example: https://github.com/simplabs/ember-simple-auth/blob/master/examples/7-multiple-external-providers.html