React Native, Node js authentication - node.js

What are the best libraries should I be using for node js + react-native authentication?
I was using passport local strategy with ejs templates for the login/register page because I was building a web app.
Now I am working on mobile application, I don't know how this is going to work on a mobile app.
Any ideas on how can I start this?
Thanks in advance.

The following is how I implement authentication in my react-native applications with node js backend. I use jwt tokens, where I store the user's token inside the device using expo's secure storage library. Then following the recommended auth flow by react-native-navigation documentation, whenever the app starts I check whether the token is present and return corresponding navigation stack. Refer to the code below for implementation.
LoginScreen.js/RegisterScreen.js
//...
// Get token from your backend and save it in secure storage
const userToken = request.data.token
try {
await SecureStore.setItemAsync("token", userToken);
} catch (error) {
console.warn(error);
}
// ...
App.js
export default function App() {
const [isReady, setIsReady] = useState(false)
const [userToken, setUserToken] = useState();
// Fetch token and set state
const restoreUser = async () => {
const token = await SecureStore.getItemAsync("token")
setUserToken(token)
}
if (!isReady)
// Show splash screen while checking for userToken
return (
<AppLoading
startAsync={restoreUser}
onFinish={() => setIsReady(true)}
onError={console.warn}
/>
);
return (
<Stack.Navigator>
{userToken == null ? (
// No token found, user isn't signed in
<Stack.Screen
name="SignIn"
component={SignInScreen}
options={{
title: 'Sign in',
// When logging out, a pop animation feels intuitive
// You can remove this if you want the default 'push' animation
animationTypeForReplace: state.isSignout ? 'pop' : 'push',
}}
/>
) : (
// User is signed in
<Stack.Screen name="Home" component={HomeScreen} />
)}
</Stack.Navigator>
);
}
On the backend take a look at jsonwebtoken npm package to see how to create jwt tokens. Goodluck!

Related

How does one secure api keys on sveltekit 1.0

I am using ghost, i made an integration and i would like to hide the api key from the front-end. I do not believe i can set restrictions on the ghost cms (that would also work). And i do believe so +page.js files are run on the browser also, so im a little confused on how to achieve this?
The interal sveltekit module $env/static/private (docs) is how you use secure API keys. Sveltekit will not allow you to import this module into client code so it provides an extra layer of safety. Vite automatically loads your enviroment variables from .env files and process.env on build and injects your key into your server side bundle.
import { API_KEY } from '$env/static/private';
// Use your secret
Sveltekit has 4 modules for accessing enviroment variables
$env/static/private (covered)
$env/static/public accessiable by server and client and injected at build (docs)
$env/dynamic/private provided by your runtime adapter; only includes variables with that do not start with the your public prefix which defaults to PUBLIC_ and can only be imported by server files (docs)
$env/dynamic/public provided by your runtime adapter; only includes variables with that do start with the your public prefix which defaults to PUBLIC_ (docs)
You don't need to hide the key.
Ghost Content API Docs:
These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data.
One common way to hide your third-party API key(s) from public view is to set up proxy API routes.
The general idea is to have your client (browser) query a proxy API route that you provide/host, have that proxy route query the third-party API using your credentials (API key), and pass on the results from the third-party API back to the client.
Because the query to the third-party API takes place exclusively on the back-end, your credentials are never exposed to the client (browser) and thus not visible to the public.
In your use case, you would have to create 3 dynamic endpoint routes to replicate the structure of Ghost's API:
src/routes/api/[resource]/+server.js to match /posts/, /authors/, /tags/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/[id]/+server.js to match /posts/{id}/, /authors/{id}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, id } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/${id}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/slug/[slug]/+server.js to match /posts/slug/{slug}/, /authors/slug/{slug}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, slug } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/slug/${slug}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
Then all you have to do is call your proxy routes in place of your original third-party API routes in your app:
// very barebones example
<script>
let uri;
let data;
async function get() {
const res = await fetch(`/api/${uri}`);
data = await res.json();
}
</script>
<input name="uri" bind:value={uri} />
<button on:click={get}>GET</button>
{data}
Note that using proxy API routes will also have the additional benefit of sidestepping potential CORS issues.

"Google Auth Library: Node.js" expects localhost:3000 as redirect uri

I am using #google-cloud/local-auth to auth an application on a node back end. I have set up a OAuth 2.0 Client IDs with the type of Web application on the google console and pointed the Authorized redirect URIs to http://localhost:3001 because that is the port I am using for dev, however I can see that the source code for #google-cloud/local-auth/build/src/index.js:56:15 clearly expects 3000
if (redirectUri.length === 0 ||
parts.port !== '3000' ||
parts.hostname !== 'localhost' ||
parts.pathname !== '/oauth2callback') {
throw new Error(invalidRedirectUri);
}
Why would this be hardcoded, what happens when you use a diferent port?
I am adding my code for context
import gsc from '#googleapis/searchconsole'
import { authenticate } from '#google-cloud/local-auth'
import { resolve } from 'path'
export default async () => {
// Obtain user credentials to use for the request
const auth = await authenticate({
keyfilePath: resolve('requests/client_secret.json'),
scopes: [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly'
]
})
google.options({ auth })
const res = await gsc.sites.list({})
console.log(res.data)
}
This library is meant to demonstrate authentication for sample purposes; it should be treated as a starting point for building an application and is not a general-purpose solution. Read it here
That port number is expected since this library is for demonstration purposes, if you are developing a production-ready application, you need to use something like https://github.com/googleapis/google-auth-library-nodejs

How to stop firebase re-auth() on every page reload in a react app?

So i have this great react app using firebase auth and firestore.
Everything working fine except
Whenever i reload the page while a user is already logged in... navbar links change for a second.
Looks like app automatically re-login(re-auth) the user on every page reload. Why so? How to get rid of it? Some look-alike code sample
import React, {useState, useEffect} from 'react';
import {Switch, Route} from 'react-router-dom'
import firebase from 'firebase/App'
export const App = () => {
const [isAuth, setIsAuth] = useState()
const auth = firebase.auth()
useEffect(() => {
auth.onAuthStateChanged(user => {
if(user) {
setIsAuth(true)
} else {
setIsAuth(false)
}
})
}, [isAuth])
return(
<div className="App">
<Navbar />
<Switch>
<Route to="/signIn" component={Login} />
<Route to="/signUp" component={SignUp} />
<Route to="/signOut" component={SignOut} />
</Switch>
</div>
)
};
Finally fixed it.
Reason it was happening bcoz firebase servers were verifying the user on each page reload which took some time and cause flickering in navbar for half a second.
Solution has three easy steps
Once logged in, store the user as boolean on local storage.
firebase.auth().onAuthStateChanged(user=>{
if (user) {
// store the user on local storage
localStorage.setItem('user', true);
} else {
// removes the user from local storage on logOut
localStorage.removeItem('user');
}
})
Check The user from local storage in navbar component
const userLocal = JSON.parse(localStorage.getItem('user'));
userLocal ? <SignedInLinks/> : <SignedOutLinks/>;
Remove user from local storage on logout
#illiterate.farmer You are almost right. Actually you should only save a flag, isSignedIn=true or false in the localStorage because saving the full user object makes your app vulnerable to hacking very easily.
Any javascript function can access the localStorage and thus it will expose you tokens that can be used to impersonate as a genuine user to your backend system.
I was having this problem too and I think Firebase's intended way of doing this is to use the FirebaseAuthConsumer... providerId is null when awaiting auth status.
Compare this sandbox where the "not signed in" content is rendered for a split second before the signed in content, with this one where no render happens until Firebase has told us whether or not the user is signed in. Will need to press the "Sign in" button on first load and then refresh to test behaviour.

React passport together

Hi I am creating my app using React and Node (express). It's time for building an authention using passport. Using ejs or other template language it looked very easy. What is the best way using React Components?
Using passport we are getting true of false {req.user} for example. I would like to send that boolean to component using react dom server, and pass it to the object like props, and transofrm that props to state and pass to state object.
What do You think about it? Is it safely ?
Then we can use condition like:
{this.state.signup ? this.welcome() : this.signupButton()}
I do not know how to do it differently. Thanks for help.
I am not an expert in react but i would say what you are doing is correct, since react provide communication between component and container is by using props and state.
The extra mile which we get by your way is once the user logout we can simply call passport logout and change the login state to false, react will manage changing ui.
this.setState({login:false});
What you are doing is correct. But make sure you modify the state in the global component (or) the Main component.
The main thing about redux state is, it resets each and every time the browser gets refreshed. so make a conditional case in the global statement and change the state.
I recommend you to use react with redux. when used with redux, all the state is a global state. you dont have to worry about the state changes.
for example (token based authentication) :
When i get the token, i save it in my browser localstorage.
localStorage.setItem('token', response.data.token);
so i have to check the token from the global component.
const token = localStorage.getItem('token');
if (token) { //redux style
store.dispatch({ type: AUTH_USER }); //dispatch is setState() in react & type is something like what action you are creating.
} //do this in your main component before you render DOM
if you dont use redux, you can use something like this
const token = localStorage.getItem('token');
if (token) { //redux style
this.setState({ /*auth: true*/ });
} //do this in your main component before you render DOM
Here is a setup using passport local, this is how I have it setup in my app.
// server route
app.post('/api/signin', passport.authenticate('local', { session:
true, failureRedirect: '/login' }), Authentication.signin);
//Authentication.signin
exports.signin = function(req, res, next) {
//user has already had their email and password authed
// we just need to give them a token
res.send({ token: tokenForUser(req.user) });
};
// Generates token for user uses jwt-simple node module
function tokenForUser(user) {
const timestamp = new Date().getTime();
return jwt.encode({ sub: user, iat: timestamp }, config.secret);
}
//then on front end in App.js
const token = localStorage.getItem('token');
if (token) {
store.dispatch({ type: types.AUTH_USER });
}

AngularJS and ExpressJS session management?

I would like to keep session across all the page. For this project, I am using expressJs, nodeJS as server side. AngularJS in front end.
I am not sure, how to handle session when view changes or url changes. Because I need to take care of both expressJS router or angularJs router.
What approach should I follow?
angularJS router
myApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/welcome', {templateUrl: 'partials/welcome.html', controller: 'MyCtrl2'});
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'MyCtrl2'});
$routeProvider.when('/signup', {templateUrl: 'partials/signup.html', controller: 'singupController'});
$routeProvider.otherwise({redirectTo: '/'});
}]);
Signup controller
myApp.controller('singupController',function($scope,$rootScope,$http){
$scope.doSingnup = function() {
var formData = {
'username' : this.username,
'password' : this.password,
'email' : null
};
var jdata = JSON.stringify(formData);
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
console.log(data);
}).
error(function(data,status,headers,config){
console.log(data)
});
}
})
ExpressJS router
module.exports = exports = function(app, db) {
var sessionHandler = new SessionHandler(db);
var contentHandler = new ContentHandler(db);
// Middleware to see if a user is logged in
app.use(sessionHandler.isLoggedInMiddleware);
app.get('/', contentHandler.displayMainPage);
app.post('/login', sessionHandler.handleLoginRequest);
app.get('/logout', sessionHandler.displayLogoutPage);
app.get("/welcome", sessionHandler.displayWelcomePage);
app.post('/signup', sessionHandler.handleSignup);
app.get('*', contentHandler.displayMainPage);
// Error handling middleware
app.use(ErrorHandler);
}
After signup, I would like to redirect to the login page. How can I do that in the above router. which one of the following should I use to change the view of app
1) $location of angularJS
2) redirect of ExpressJS
So i had the same problem and to be fair i might have read the approach somewhere i don't remember anymore.
Problem: Angular builds single page apps. After refresh, you loose scope and with it the authenticated user.
Approach
AngularJS modules offer a startup function called run which is called always when the page is loaded. Perfect for refresh/reload.
myApp.run(function ($rootScope, $location, myFactory) {
$http.get('/confirm-login')
.success(function (user) {
if (user && user.userId) {
$rootScope.user = user;
}
});
}
express-session saves the sessions for you and authenticates you with the sessionId your browser sends. So it always knows if you are authenticated or not.
router.get('/confirm-login', function (req, res) {
res.send(req.user)
}
);
All i had to do is, after refreshing and all dependencies were loaded, ask if i am authenticated and set $rootScope.user = authenticatedUserFromExpress;
There are two different concepts here - server side session state and the user state on the client side in Angular. In express you can use the session via req.session to manage session based data.
On the angular side, there is only scope in your controllers. If you want to keep track of some data across multiple controllers, you need to create a service to store the data in and inject the service into the controllers you need.
A typical lifecycle is to first check if there is data already in the service, if so use it. If not, wait for the data to be populated (by the user or app or whatever) then detect those changes and synchronize with your service.
signup controller
function SignupCtrl($scope, $http, $location) {
$scope.form = {}; // to capture data in form
$scope.errorMessage = ''; // to display error msg if have any
$scope.submitPost = function() { // this is to submit your form can't do on
//traditional way because it against angularjs SPA
$http.post('/signup', $scope.form).
success(function(data) { // if success then redirect to "/" status code 200
$location.path('/');
}).error(function(err) { // if error display error message status code 400
// the form can't be submitted until get the status code 200
$scope.errorMessage = err;
});
};
}
sessionHandler.handleSignup
this.handleSignup = function(req, res, next) {
"use strict";
// if you have a validate function pass the data from your
// Signup controller to the function in my case is validateSignup
// req.body is what you need
validateSignup(req.body, function(error, data) {
if(error) {
res.send(400, error.message); // if error send error message to angularjs
}else {
// do something else
// rmb to res.send(200)
}
});
}
validatesignup
function validateSignup(data,callback) {
"use strict"; // the data is req.body
//so now you can access your data on your form
// e.g you have 2 fields name="password" and name="confirmPassword on your form"
var pass = data.password,
comPass = data.confirmPassword;
if(pass != comPass){
callback(new Error('Password must match'), null);
// then show the error msg on the form by using
//angular ng-if like <div ng-if="errorMessage">{{errorMessage}}</div>
}else{
callback(null, data);
}
}
hope this help
Of all the answers here, I like #alknows's approach best. However, like the other answers that suggest you send a request to the server to get the current user data, there are a couple issues I take with them:
You have to deal with race conditions as a result of your AJAX ($http) call.
You're sending an unnecessary request to the server after it already rendered your index.html
I tried #alknow's approach and it worked out for me after I was able to resolve the many race conditions that came up as a result of my angular app controllers and config needing the current user to do their job. I try my best to avoid race conditions when appropriate, so I was a bit reluctant to continue with this approach. So I thought of a better approach: send the current user data down with your index.html and store it locally.
My Approach: Embed currentUser in index.html & store locally on client
In index.html on your server, make a script tag to hold whatever data you want to pass to the client:
```
<!--YOUR OTHER index.html stuff go above here-->
<script id="server-side-rendered-client-data" type="text/javascript">
var __ssr__CData = {
currentUser: { id: '12345', username: 'coolguy', etc: 'etc.' }
}
</script>
```
Then, as #alknows suggested, in app.js or wherever you initiate your angular app, add app.run(..., () => {...}). In app.run(), you will want to grab the server side rendered client data object, which I named obscurely __ssr_CData so that I am less likely to run into name collisions across the global namespace later in my other javascript:
var myAngularApp = angular.module("mainApp", ['ngRoute']);
myAngularApp.run(function ($rootScope) {
const currentUserFromServer = __ssr__CData.currentUser
const currentUserAccessTokenFromServer = __ssr__CData.accessToken
const currentUser =
CurrentUser.set(currentUserAccessTokenFromServer, currentUserFromServer)
$rootScope.currentUser = currentUser
});
As you know app.run() will be called whenever the page does a full reload. CurrentUser is a global class for managing my angular app's current user in the single page environment. So when I call CurrentUser.set(...) it stores the current user data in a place I can retrieve later in my angular app by calling CurrentUser.get(). So in any of your angular app controller's you can now retrieve the current user the server provided by simply doing this:
myAngularApp.controller('loginController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user log in
//...
}
In that example, I made use of CurrentUser.get(), which I explained above, to get the previously stored current user from the server. I could have also retrieved that current user by accessing $rootScope.currentUser because I stored it there, too. It's up to you.
myAngularApp.controller('signupController',function($scope, $rootScope, $http){
//check if the user is already logged in:
var currentUser = CurrentUser.get()
if(currentUser) {
alert("HEY! You're already logged in as " +currentUser.username)
return $window.location.href = "/";
}
//there is no current user, so let user signup
//... you run your signup code after getting form data
$http({method:'POST',url:'/signup',data:jdata})
.success(function(data,status,headers,config){
//signup succeeded!
//set the current user locally just like in app.js
CurrentUser.set(data.newUser)
//send user to profile
return $window.location.href = "/profile";
})
.error(function(data,status,headers,config){
//something went wrong
console.log(data)
});
}
Now, after a new user has signed up, your server returned the new user from the AJAX call. We set that new user as the current user by calling CurrentUser.set(...) and send the user to their profile. You can now get the current user in the profile controller the same way you did to check if the current user existed in the login and signup controllers.
I hope this helps anyone who comes across this. For your reference, I'm using the client-sessions module to handle sessions on my server.

Resources