Dears,
I'm trying to find how managing authentification on client side using the hhtp-only cookie sent by the server.
What I don't understand is that since the HTTP only cookie can't be accessed by the front end, how the front end knows that the user is (still) authenticated ?
So far, the only solution if found is to send to the client a token when the authentication succeed. And keep this token in a second cookie created by the client.
But it seems to me that I'm doing the same job twice.
1- managing the HTTP only cookie on server side, especially the expiration date
2- managing also on client side the expiration date of the second cookie.
How can avoid this ? I'd like to manage the authentification on client side based on the HTTP only server cookie. If there is a server cookie, then go on, else redirect to login page.
I'm using node/express on server side and react on client one. The session is stored in redis, both sides are HTTPS using certificates.
Thks
You don't need to store another cookie.
I suppose you use token based authentication on your endpoint, eg. JWT. Then you think about this scenario:
User send username/password to server.
Check user credentials and if there are valid, create http-only cookie with the token
const user = await getUser({ where: { email } });
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
throw new UserInputError('Form Arguments invalid', {
invalidArgs: {
'password': 'Invalid password!',
},
});
}
const token = jwt.sign({ userId: user.id }, process.env.APP_SECRET);
/
res.cookie('token', token, {
httpOnly: true,
maxAge: 1000 * 60 * 60 * 24 * 365,
});
Write auth middlerware to put the userId onto the req for future requests to access
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server');
module.exports = async function(req, res, next) {
const { token } = req.cookies;
if (token) {
try {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
if (!userId) return next();
req.userId = userId;
} catch (e) {
console.log(e);
}
}
next();
};
Check on each request the userId. If there is no userId, user doesn't logged in
if (!req.userId) {
throw new AuthenticationError('Log in!');
}
If user's token is invalid/expired you will get AuthenticationError. Catch it and redirect to login page.
If your UI depends on user status, you can create easy-to-use component (i am using React) to check it.
User Component:
import { Query } from 'react-apollo';
import gql from 'graphql-tag';
import PropTypes from 'prop-types';
const CURRENT_USER_QUERY = gql`
query CURRENT_USER_QUERY {
me {
userId
firstName
lastName
profilePictureUrl
}
}
`;
const User = props => (
<Query {...props} query={CURRENT_USER_QUERY} fetchPolicy={'cache-first'}>
{payload => props.children(payload)}
</Query>
);
User.propTypes = {
children: PropTypes.func.isRequired,
};
export default User;
If we get me object from server, you know, there is a logged in user, so you can render depends on user's status:
import { Link } from 'react-router-dom';
import React from 'react';
<User>
{({ loading, error, data: { me } }) => {
if (loading || error || !me) return (
<Button component={Link} to={'/login'}>Login</Button>
);
if(me) return (
<Button component={Link} to={'/dashboard'}>Go to dashboard</Button>
)
}}
</User>
Related
i have now stored my jwt in cookies when user sign in or sign up but the data don't stay so i made a function to handle this but i need the value of the token to make it work
this is the function that i need token value for
const setAuthToken = (token) => {
if (token) {
axios.defaults.headers.common['x-auth-token'] = token;
} else {
delete axios.defaults.headers.common['x-auth-token'];
}
};
and this is my action that i use in react to send the token value to this function i tried to use js-cookies for that but it give me undefined
import Cookies from 'js-cookie';
//load user
export const loadUser = () => async (dispatch) => {
const token = Cookies.get('access_token');
console.log(token);
// if (cookie.access_token) {
// setAuthToken(cookie.access_token);
// }
try {
const res = await axios.get('/user/me');
dispatch({
type: USER_LOADED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
and this is my recieved cookie in browser
If you take a close look at your screenshot, you can see that the cookie is sent by the server as HttpOnly. This is a security measure, and therefore the cookie isn't accessible to any JavaScript code by design.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Restrict_access_to_cookies
If you are in control of the server, you could change it accordingly, if not you will have to make a deal :-)
res.cookie('x-auth-token',token,{
maxAge: 3600,
httpOnly: true,
secure:true
})
I notice that the docs specify that I can create a token to expire up to 3600 seconds later[1] But I don't see how to do that with auth().createCustomToken ... I can manually do it with jsonwektoken, but it seems like this should be addressable directly with firebase-admin library.
Another question is, what is the secret I need to verify my own token generated in this way, the uid ?
index.js
// demo server generating custom auth for firebase
import Koa from 'koa'
import Koajwt from 'koa-jwt'
import Token from './token'
const app = new Koa()
// Custom 401 handling if you don't want to expose koa-jwt errors to users
app.use(function(ctx, next){
return next().catch((err) => {
if (401 == err.status) {
ctx.status = 401
ctx.body = 'Protected resource, use Authorization header to get access\n'
} else {
throw err
}
})
})
// Unprotected middleware
app.use(function(ctx, next){
if (ctx.url.match(/^\/login/)) {
// use router , post, https to securely send an id
const conf = {
uid: 'sample-user-uid',
claims: {
// Optional custom claims to include in the Security Rules auth / request.auth variables
appid: 'sample-app-uid'
}
}
ctx.body = {
token: Token.generateJWT(conf)
}
} else {
return next();
}
});
// Middleware below this line is only reached if JWT token is valid
app.use(Koajwt({ secret: 'shared-secret' }))
// Protected middleware
app.use(function(ctx){
if (ctx.url.match(/^\/api/)) {
ctx.body = 'protected\n'
}
})
app.listen(3000);
token.js
//import jwt from 'jsonwebtoken'
import FirebaseAdmin from 'firebase-admin'
import serviceAccount from 'demo-admin-firebase-adminsdk-$$$$-$$$$$$.json'
export default {
isInitialized: false,
init() {
FirebaseAdmin.credential.cert(serviceAccount)
isInitialized = true
},
/* generateJWTprimiative (payload, signature, conf) {
// like: jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: '15m' })
jwt.sign(payload, signature, conf)
} */
generateJWT (conf) {
if(! this.isInitialized)
init()
FirebaseAdmin.auth().createCustomToken(conf.uid, conf.claims)
.then(token => {
return token
})
.catch(err => {
console.log('no token generate because', err)
})
}
}
[1] https://firebase.google.com/docs/auth/admin/create-custom-tokens
You can't change the token expiration. The docs you found includes the words:
Firebase tokens comply with the OpenID Connect JWT spec, which means
the following claims are reserved and cannot be specified within the
additional claims:
... exp ...
This is further backed up by inspecting the Firebase Admin SDK source code on GitHub.
In this section:
public createCustomToken(uid: string, developerClaims?: {[key: string]: any}): Promise<string> {
// .... cut for length ....
const header: JWTHeader = {
alg: ALGORITHM_RS256,
typ: 'JWT',
};
const iat = Math.floor(Date.now() / 1000);
const body: JWTBody = {
aud: FIREBASE_AUDIENCE,
iat,
exp: iat + ONE_HOUR_IN_SECONDS,
iss: account,
sub: account,
uid,
};
if (Object.keys(claims).length > 0) {
body.claims = claims;
}
// .... cut for length ....
You can see the exp property is hard coded to be iat + ONE_HOUR_IN_SECONDS where the constant is defined elsewhere in the code as 60 * 60...
If you want to customize the expiration time, you will HAVE to create your own token via a 3rd party JWT package.
To your 2nd question, a secret is typically stored in the server environment variables, and is a pre-set string or password. Technically you could use the UID as the secret, but that would be a TERRIBLE idea security wise - please don't do this. Your secret should be like your password, keep it secure and don't upload it with your source code to GitHub. You can read more about setting and retrieving environment variables in Firebase in these docs here
So I have an app that logs me in via Auth0 and saves a jwt token in a cookie.
I also have an Apollo Server 2 that retrieves the data. How do I secure the Apollo Server and only return data if the user is logged in and verified by the Auth0 server?
The code below comes right from https://www.apollographql.com, but what I don't understand is how to handle getUser(token) below to actually check for a valid JWT in the Authorization header, and if present, the user will be allowed to access protected resources?
// using apollo-server 2.x
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// get the user token from the headers
const token = req.headers.authorization || '';
// try to retrieve a user with the token
const user = getUser(token);
// add the user to the context
return { user };
},
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`)
});
getUser is the method that returns your user with the given token. you might need to write that method yourself or use OAuth's getUser method.
After getting the user object, you're returning it so now you have access to the user object in your resolvers. In your resolver method, the third parameter is your context object. you can access the user object there. If you want to protect that resolver to only be allowed by logged in users you can throw an error if user is null or undefined.
For example:
export const resolvers = {
Query: {
Me: (parent, args, { user }) => {
if (!user) return Error(`Not Logged In!`)
return user
}
}
}
At the moment I am creating a Firebase API on nodejs. I would like to handle all Firebase stuff (like authentication) with firebase-admin on nodejs. But what is the correct way to authenticate a user over nodejs in firebase-admin without the Javascript Firebase SDK on the client side? On the official documentation for admin I didn't find a function called signInWithEmailAndPassword (like as on the client side SDK) for nodejs. There is only a function called: "getUserByEmail", but this function doesn't check if the user has entered the correct password.
This is my form:
<form class="sign-box" action="/login" method="post">
<div class="form-group">
<input id="username" name="username" type="text" class="form-control" placeholder="E-Mail"/>
</div>
<div class="form-group">
<input id="password" name="password" type="password" class="form-control" placeholder="Password"/>
</div>
<button type="submit" class="btn btn-rounded">Sign in</button>
</form>
Once the form is submitted I pass the values to my API in nodejs:
app.post('/login', urlencodedParser, function (req, res) {
// getting the values
response = {
username: req.body.username,
password: req.body.password
};
// authenticate the user here, but how ?
});
My first idea was to use the Firebase SDK on the client side to sign in with signInWithEmailAndPassword and to get the uid. Once I had the UID I wanted to sent the UID to nodejs and call the function createCustomToken and to return the generated token (with some additional claims) back to the client. Once I get the token back I would use the function signWithCustomToken (on the client side) to authenticate the user. Is this way correct or is there a better way ?
Actually for authentication you will need to use the firebase regular api, no the admin.
First this will give you a refreshed firebase token, not a custom token.
If you like you can do the same to obtain a custom token, if you need a custom token, I also have an example.
npm install firebase --save
const firebase = require("firebase");
const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: ""
};
firebase.initializeApp(config);
I am posting my login firebase function but you will be able to change it to express easily.
exports.login = functions.https.onRequest((req, rsp)=>{
const email = req.body.email;
const password = req.body.password;
const key = req.body.key;
const _key = '_my_key_';
let token = '';
if(key === _key){
firebase.auth().signInWithEmailAndPassword(email,password).then((user)=>{
//The promise sends me a user object, now I get the token, and refresh it by sending true (obviously another promise)
user.getIdToken(true).then((token)=>{
rsp.writeHead(200, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({token:token}));
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
}).catch((err)=>{
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify({error:err}));
});
} else {
rsp.writeHead(500, {"Content-Type": "application/json"});
rsp.end(JSON.stringify('error - no key'));
}
});
NOTE: I am using this login function to test my other functions with Postman, so that is why i am sending a key, so I can use this privately.
Now combining the ADMIN and FIREBASE node apy I am able to do a lot of very interesting stuff with HTTP functions on my firebase.
Hope it helps somehow.
For Any Server Side React Users
I was brought here because I was attempting to authenticate users in firebase without the Javascript Firebase SDK on the client side as well. I am building a server side rendered react app. The client-side firebase.auth() does not work on a server-side node environment.
It turns out that you can run firebase.auth() commands inside of componentDidMount(), because that does not run on the server. This is where you can authenticate and get your user's token, and then send it to a cloud function for any server-side rendering that requires user authentication.
On the server side, you can then verify the token with the admin sdk.
You will also need to require firebase/app and firebase/auth, and initialize firebase in your browser-specific bundle.js, so that it is not included in your server's bundle.js
componentDidMount() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
console.log("User signed in!");
} else {
console.log("User NOT signed in!");
}
});
}
The officially recommended and supported way is to use ID TOKENS
From the official docs:
If your Firebase client app communicates with a custom backend server, you might need to identify the currently signed-in user on that server. To do so securely, after a successful sign-in, send the user's ID token to your server using HTTPS. Then, on the server, verify the integrity and authenticity of the ID token and retrieve the uid from it. You can use the uid transmitted in this way to securely identify the currently signed-in user on your server.
The workflow is:
Use the Firebase Web SDK in your client
The user logs in with any of the authentication methods
Retrieve the ID token on the client
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
Send this token to the server
Server verifies the ID token with the Firebase Admin SDK
// idToken comes from the client app
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// ...
})
.catch((error) => {
// Handle error
});
your user is securely authenticated and uniquely identified
This is my solution, maybe it can help someone (Node/react). For some reason the client side method signInWithEmailAndPassword seems to work both on the client AND server. Basically this lets you keep the default security rule ".read": "auth != null" without having to use signInAnonymously() hence avoid creating an infinite number of stale users.
server:
const { firebase } = require('../../firebase/frontend');
const { firebase: admin } = require('../../firebase/backend');
const email = process.env.READ_ONLY_EMAIL;
const password = process.env.READ_ONLY_PASSWORD;
export default async (req, res) => {
try {
const { user } = await firebase.auth().signInWithEmailAndPassword(email, password);
const customToken = await admin.auth().createCustomToken(user.uid);
return res.status(200).send(JSON.stringify(customToken));
} catch (error) {
return res.status(404).send(error);
}
};
client:
import fetch from 'isomorphic-unfetch';
import { useState, useEffect } from 'react';
import { firebase } from '../firebase/frontend';
const useUser = (props) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [isAnonymous, setIsAnonymous] = useState(true);
const getCustomToken = async () => {
const response = await fetch('/api/auth', { method: 'POST' });
const json = await response.json();
return json;
};
useEffect(() => {
try {
const unsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
// user exists
if (user && user.email !== 'readonly#myEmailAddress.com') {
setUser(user);
setIsAnonymous(false);
// else sign in user "anonymously"
} else {
setIsAnonymous(true);
const token = await getCustomToken();
firebase.auth().signInWithCustomToken(token);
}
setLoading(false);
});
return () => unsubscribe();
} catch (error) {
console.log('Error signing in user', error);
}
}, []);
return {
user,
isAnonymous,
loading
// etc...
};
};
export default useUser;
So far I have only dealt with server-rendered apps, where after a user logs in via username/password or using an OAuth provider (Facebook etc.), the server just sets a session cookie while redirecting to the relevant page.
However now I'm attempting to build an app using a more 'modern' approach, with React on the frontend and a JSON API backend. Apparently the standard choice for this is to use a JSON web token for authentication, however I'm having trouble working out how I'm meant to provide the JWT to the client so it can be stored in session/local storage or wherever.
Example to illustrate better:
User clicks link (/auth/facebook) to log in via Facebook
User is redirected and shown Facebook login form and/or permission dialog (if necessary)
Facebook redirects user back to /auth/facebook/callback with an authorization code in tow, the server exchanges this for an access token and some information about the user
Server finds or creates the user in the DB using the info, then creates a JWT containing a relevant subset of the user data (e.g. ID)
???
At this point I just want the user to be redirected to the main page for the React app (let's say /app) with the JWT in tow, so the frontend can take over. But I can't think of an (elegant) way to do that without losing the JWT along the way, other than to put it in the query string for the redirect (/app?authtoken=...) - but that will display in the address bar until I remove it manually using replaceState() or whatever, and seems a little weird to me.
Really I'm just wondering how this is typically done, and I'm almost sure I'm missing something here. The server is Node (Koa with Passport), if that helps.
Edit: To be clear, I'm asking what the best way is to provide a token to the client (so it can be saved) after an OAuth redirect flow using Passport.
I recently ran across this same issue, and, not finding a solution here or elsewhere, wrote this blog post with my in-depth thoughts.
TL;DR: I came up with 3 possible approaches to send the JWT to the client after OAuth logins/redirects:
Save the JWT in a cookie, then extract it on the front-end or server in a future step (eg. extract it on the client with JS, or send a request to the server, server uses the cookie to get the JWT, returns the JWT).
Send the JWT back as part of the query string (which you suggest in your question).
Send back a server-rendered HTML page with a <script> tag that:
Automatically saves the embedded JWT to localStorage
Automatically redirects the client to whatever page you like after that.
(Since logging in with JWTs is essentially equivalent to "saving the JWT to localStorage, my favorite option was #3, but it's possible there are downsides I haven't considered. I'm interested in hearing what others think here.)
Hope that helps!
Client: Open a popup window via $auth.authenticate('provider name').
Client: Sign in with that provider, if necessary, then authorize the application.
Client: After successful authorization, the popup is redirected back to your app, e.g. http://localhost:3000, with the code (authorization code) query string parameter.
Client: The code parameter is sent back to the parent window that opened the popup.
Client: Parent window closes the popup and sends a POST request to /auth/provider withcode parameter.
Server: Authorization code is exchanged for access token.
Server: User information is retrived using the access token from Step 6.
Server: Look up the user by their unique Provider ID. If user already exists, grab the existing user, otherwise create a new user account.
Server: In both cases of Step 8, create a JSON Web Token and send it back to the client.
Client: Parse the token and save it to Local Storage for subsequent use after page reload.
Log out
Client: Remove token from Local Storage
here is a login request from the server side. it's storing the token in the header:
router.post('/api/users/login', function (req, res) {
var body = _.pick(req.body, 'username', 'password');
var userInfo;
models.User.authenticate(body).then(function (user) {
var token = user.generateToken('authentication');
userInfo = user;
return models.Token.create({
token: token
});
}).then(function (tokenInstance) {
res.header('Auth', tokenInstance.get('token')).json(userInfo.toPublicJSON());
}).catch(function () {
res.status(401).send();
});
});
here is the login request on the react side, where I am grabbing the token from the header and setting the token in local storage once the username and password pass authentication:
handleNewData (creds) {
const { authenticated } = this.state;
const loginUser = {
username: creds.username,
password: creds.password
}
fetch('/api/users/login', {
method: 'post',
body: JSON.stringify(loginUser),
headers: {
'Authorization': 'Basic'+btoa('username:password'),
'content-type': 'application/json',
'accept': 'application/json'
},
credentials: 'include'
}).then((response) => {
if (response.statusText === "OK"){
localStorage.setItem('token', response.headers.get('Auth'));
browserHistory.push('route');
response.json();
} else {
alert ('Incorrect Login Credentials');
}
})
}
When you get a token from any passport authentication sites you have to save the token in your browser's localStorage. The Dispatch is Redux's Middleware. Ignore dispatch if you don't use redux in your app. you can just use setState here (A bit weird without redux).
Client-side:
Here's something similar API of mine, which returns token.
saving tokens
axios.post(`${ROOT_URL}/api/signin`, { email, password })
.then(response => {
dispatch({ type: AUTH_USER }); //setting state (Redux's Style)
localStorage.setItem('token', response.data.token); //saving token
browserHistory.push('/home'); //pushes back the user after storing token
})
.catch(error => {
var ERROR_DATA;
try{
ERROR_DATA = JSON.parse(error.response.request.response).error;
}
catch(error) {
ERROR_DATA = 'SOMETHING WENT WRONG';
}
dispatch(authError(ERROR_DATA)); //throw error (Redux's Style)
});
So When you make some authenticated requests,you have to attach the token with the request in this form.
authenticated requests
axios.get(`${ROOT_URL}/api/blog/${blogId}`, {
headers: { authorization: localStorage.getItem('token') }
//take the token from localStorage and put it on headers ('authorization is my own header')
})
.then(response => {
dispatch({
type: FETCH_BLOG,
payload: response.data
});
})
.catch(error => {
console.log(error);
});
Here's my index.js:
The token is checked each and everytime, so even if the browser got refreshed, you can still set the state.
checks if the user is authenticated
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: AUTH_USER })
}
ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/" component={App}>
..
..
..
<Route path="/blog/:blogid" component={RequireAuth(Blog)} />
//ignore this requireAuth - that's another component, checks if a user is authenticated. if not pushes to the index route
</Route>
</Router>
</Provider>
, document.querySelector('.container'));
All that dispach actions does is it sets the state.
my reducer file(Redux only) else you can just use setState() in your index route file to provide the state to the whole application. Every time the dispatch is called, it runs a similar reducer file like this which sets the state.
setting the state
import { AUTH_USER, UNAUTH_USER, AUTH_ERROR } from '../actions/types';
export default function(state = {}, action) {
switch(action.type) {
case AUTH_USER:
return { ...state, error: '', authenticated: true };
case UNAUTH_USER:
return { ...state, error: '', authenticated: false };
case AUTH_ERROR:
return { ...state, error: action.payload };
}
return state;
} //you can skip this and use setState() in your index route instead
Delete the token from your localStorage to logout.
caution: Use any different name rather than token to save the token in your browser's localStorage
Server-Side:
considering your passport services file. You must set the header search.
Here's passport.js
const passport = require('passport');
const ExtractJwt = require('passport-jwt').ExtractJwt;
const JwtStrategy = require('passport-jwt').Strategy;
..
..
..
..
const jwtOptions = {
jwtFromRequest: ExtractJwt.fromHeader('authorization'), //client's side must specify this header
secretOrKey: config.secret
};
const JWTVerify = new JwtStrategy(jwtOptions, (payload, done) => {
User.findById(payload._id, (err, user) => {
if (err) { done(err, null); }
if (user) {
done(null, user);
} else {
done(null, false);
}
});
});
passport.use(JWTVerify);
In my router.js
const passportService = require('./services/passport');
const requireAuthentication = passport.authenticate('jwt', { session: false });
..
..
..
//for example the api router the above react action used
app.get('/api/blog/:blogId', requireAuthentication, BlogController.getBlog);