How to implement optional authentication in React/Express app? - reactjs

I'm building self-hosted bookmarks manager app. I'm using React on frontend and Express on backend. I have already implemented normal authentication system based on JWT. But now I want to make authentication optional. Beacuse it's self-hosted app some users might want to use it only on local network = auth disabled, and other expose it to the Internet = auth enabled.
On backend, I'm using env variable USE_AUTH=false to disable authentication and everything works as it should.
On frontend however, I'm rendering some components and routes based on isAuthenticated value stored in AuthContext state. It is set to true on successful login/sign up and to false on logout.
My question is how can I tell my React frontend that I'm not using auth on backend so it can render all hidden components, don't include token to requests etc.?
The only solution I came up with, is to send request when user opens the app to ask server if it's using authentication. So something like GET /api/auth/ping will return { useAuth: true } or { useAuth: false } depending on USE_AUTH value.
But I don't know if it is as optimal solution. Is there a better option / pattern I can use?
Thanks

I believe the correct way to do this is to specify in your react app, which routes are private and which ones are public, depending on that, components will be rendered.

Related

Nextjs - Correct way to add auth access for catch-all routes

I’m not sure if “catch all” would be the correct term here, but how do you block access to page routes (not API) including any slug that comes after the first slug. Also, I’m using Nextjs and NextAuth
Ie. Allow all-auth-users access but not public access to:
/account/userid
Ie. Allow only logged in session user access to:
/account/userid/actions
Ie. Still only allow logged in session user access to:
/account/userid/actions/create
The only way I can think of doing this is by detecting router paths via useRouter and then adding specific wrapper components with session logic around /account/userid/ and different session logic for around /account/userid/actions.
Is this the correct/best way to go about this?
You can configure a middleware.ts file and define which routes you wish users to have access to. More on nextjs middleware here https://nextjs.org/docs/advanced-features/middleware. And as for authorization of each route via middleware, you can read more about that here https://next-auth.js.org/configuration/nextjs

Custom React GUI for oidc-client-js

is there a way to user your custom React GUI with oidc-client-js? I know that if you trigger authentication endpoint using:
// PopUps might be blocked by the user, fallback to redirect
try {
await this.userManager.signinRedirect(this.createArguments(state)); //Shows midleware login form
return this.redirect();
} catch (redirectError) {
console.log("Redirect authentication error: ", redirectError);
return this.error(redirectError);
}
Middleware will try to render its predefined login form:
However I have my own React form and I only need to pass to OICDClient params (email,password) and get back User instance to display UserName etc. Something like:
var loggedUser = await this.userManager.signinCustom(state.loginEmail, state.LoginPassword); //Login using credentials
I don't want to write all the logic by myself I really want to use all functionality from OIDCClient - only with my GUI (loginForm, registerForm, updateUserForm etc).
I'm using scaffolded library from MSDN using command:
dotnet new react -o <output_directory_name> -au Individual
Is there any method/implementation to initialise oidc-client-js from React components and not user default GUI forms?
Thanks a lot!
I might be missing some thing but the whole idea of using a 3rd partly federated auth provider be it your own/your company's SSO (say developed using Identity Server 4) or say Google sign in(say using their Firebase JS kit) or Microsoft sign in, Facebook sign in etc. is that you will be redirected to their authentication endpoint where you then use say your google credentials (if you are using google sign in for example) to sign on to google auth servers. Once you do that then a payload (consisting of an identity token and access token) is returned back to your redirect URL which you must configure for your specific app.
By saying you'd like to provide your own sign-in form you are missing the entire point of using a 3rd party authentication provider. Besides, you/your app shouldn't know about user names and passwords and you don't want to have access to all that information. All that you should be interested in knowing whether the user, who are using one of the federated authentication providers, that you would have configured for your app, are who they claim to be and you then delegate all that heavy lifting to your 3rd party authentication provider.
Besides, say your company has a SSO system of their own then all your company's app will use that SSO system and from a UI perspective you want to show the same login screen so as to give a consistent user experience.
In addition, if you show me a google authentication button and then on clicking that button you show me some weird form that doesn't look like the typical google picklist sign-in form then I'll shut down your app before you can say hello, and, I suspect most user would (and should) do the same. The reason being that when you show me a google sign-in page then I know that you/your app will only get back what you need and I wouldn't ever entrust my google user name and password to any other application.
If you really want to have your own authentication form then you'll have to manage user names and passwords yourself, the way we used to do things probably over 10+ years back.
If you decide to go the route of setting up your own authentication system and then say use identity server 4 then yes you can certainly change the UI and customize it to your heart's content, but that will happen at the server level, not at the level of your react app. Point being that in any SSO system the user will be redirected to the that auth provider's endpoint where they then authenticate (and, optionally, provider permission for your app to use certain claims) and once they do that they they are redirected back to your redirect endpoint with a payload (JWT token).
Lastly, even if you could some how wire up a client side sign in form, I'm not sure you would want to use it. Passing passwords & user names over the wire isn't a good idea. You'll always want to use a server rendered sign in form.

Can the Structure of a React App be hidden behind a login?

Our React App is secured by a login. This prevents attackers from accessing user-specific content because they are unable to begin a session without credentials.
We are now ready to start testing with real world users. For this we would like to host our app on a publicly accessible endpoint. But we cannot allow unauthorized individuals to explore our app. Of course they would not get to see user data but they could potentially view our apps structure and features and steal our product, correct?
Is there a way to prevent this or should we instead only allow access from a VPN for the initial test phase?
React is a bundled application. That means your entire react app is in a single javascript file. Even if you had a login on the react app, someone could still just... bypass it.
A way to prevent this would probably be just a simple nodejs server which has a login:
app.get('/secretapp/:password', (Req,Res) => {
if (Req.params.password == "hunter2") {
Res.send("react_app.js")
} else {
Res.status(404).end() // What route? Nothing to see here. Go away.
}
})
Otherwise, a VPN is not a bad choice.

Next.js Authentication Strategies

I've been trying to implement a reliable authentication flow for a Next.js project but I'm completely lost now. I've already seen the examples repo of Next.js. But I have a lot of questions for a complete solution.
I have a express.js API and a separate Next.js frontend project. All the data and the authentication is handled by the API. Frontend just renders the pages with SSR. If I would just create a monolith project, where rendering the pages and all the data is handled by a single server (with a custom server option for Next.js I mean), I would just use express-session and csurf. It would be a traditional way to manage sessions and create security against CSRF.
Express.js API is not a requirement. It is just an example. It could be a Django API, or a .Net Core API. The main point is, it is a separate server and a separate project.
How can I have a simple, yet reliable structure? I've examined some of my favorite websites (netlify, zeit.co, heroku, spectrum.chat etc). Some of them use localstorage to store access and refresh tokens (XSS vulnerable). Some of them use cookies and they are not even HTTPOnly (both XSS and CSRF vulnerable). And examples like spectrum.chat use the way I mentioned above (cookie-session + preventing csrf).
I know there is the giant hype around the JWT tokens. But I find them too complex. Most of the tutorials just skips all the expiration, token refreshing, token revocation, blacklisting, whitelisting etc.
And many of the session cookie examples for Next.js almost never mention CSRF. Honestly, authentication is always a big problem for me. One day I read that HTTPOnly cookies should be used, next day I see a giant popular site not even using them. Or they say "never store your tokens to localStorage", and boom some giant project just uses this method.
Can anyone show me some direction for this situation?
Disclaimer: I am a maintainer of the free open source package below, but I think it's appropriate here as it's a common question there isn't a great answer for, as many of the popular solutions have the specific security flaws raised in the question (such as not using CSRF where appropriate and exposing Session Tokens or web tokens to client side JavaScript).
The package NextAuth.js attempts to address the issues raised above, with free open source software.
It uses httpOnly cookies with secure.
It has CSRF protection (double submit cookie method, with signed cookies).
Cookies are prefixed as appropriate (e.g. __HOST- or __Secure).
It supports email/passwordless signin and OAuth providers (with many included).
It supports both JSON Web Tokens (signed + encrypted) and Session Databases.
You can use it without a database (e.g. any ANSI SQL, MongoDB).
Has a live demo (view source).
It is 100% FOSS, it is not commercial software or a SaaS solution (is not selling anything).
Example API Route
e.g. page/api/auth/[...nextauth.js]
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
const options = {
providers: [
// OAuth authentication providers
Providers.Apple({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET
}),
Providers.Google({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET
}),
// Sign in with email (passwordless)
Providers.Email({
server: process.env.MAIL_SERVER,
from: '<no-reply#example.com>'
}),
],
// MySQL, Postgres or MongoDB database (or leave empty)
database: process.env.DATABASE_URL
}
export default (req, res) => NextAuth(req, res, options)
Example React Component
e.g. pages/index.js
import React from 'react'
import {
useSession,
signin,
signout
} from 'next-auth/client'
export default () => {
const [ session, loading ] = useSession()
return <p>
{!session && <>
Not signed in <br/>
<button onClick={signin}>Sign in</button>
</>}
{session && <>
Signed in as {session.user.email} <br/>
<button onClick={signout}>Sign out</button>
</>}
</p>
}
Even if you don't choose to use it, you may find the code useful as a reference (e.g. how JSON Web Tokens are handled and how they are rotated in sessions.
I've had to think about this as well for my current project. I use the same technologies: an ExpressJS API and a NextJS server-side-rendered front-end.
What I chose to do is use passport.js in the ExpressJS API. TheNetNinja on YouTube has a really good playlist of this with 21 episodes. He shows you how to implement Google OAuth 2.0 in your API, but this logic transfers to any other strategy (JWT, Email + Password, Facebook authentication etc.).
In the front-end, I would literally redirect the user to a url in the Express API. This url would show the user the Google OAuth screen, the user clicks on "Allow", the API does some more stuff, makes a cookie for the specific user and then redirects back to a url in the front end. Now, the user is authenticated.
About HTTPOnly cookies: I chose to turn off this feature, because I was storing information in the cookie that I needed in the front-end. If you have this feature enabled, then the front-end (javascript) doesn't have access to those cookies, because they are HTTPOnly.
Here's the link to the playlist I was talking about:
https://www.youtube.com/watch?v=sakQbeRjgwg&list=PL4cUxeGkcC9jdm7QX143aMLAqyM-jTZ2x
Hope I've given you a direction you can take.
EDIT:
I haven't answered your question about CSURF, but that's because I'm not familiar with it.
I've finally found a solution!
Now I'm using csrf npm package, not csurf. csurf is just turns csrf into an express middleware.
So, I create a csrfSecret in the getInitialProps of _app. It creates the secret, sets it as a httpOnly cookie. Later, it creates a csrfToken and returns it with pageProps. So, I can access it with window.NEXT_DATA.props.csrfToken. If user refreshes the page, csrfSecret remains the same, but csrfToken gets renewed.
When I make a request to the proxied "/api/graphql API route, it first gets the csrf token from x-xsrf-token header and verifies it with the csrfSecret cookie value.
After that, it extracts the value of authToken cookie and passes it to the actual GraphQL API.
API is all token based. It only needs a non-expiring access token. (BTW, It doesn't need to be JWT. Any cryptographically strong, random token can be used. Which means a reference/opaque token.)
CSRF check is not needed for the actual API, because it doesn't rely on cookies for authentication. It only checks authorization header.
Both authToken and csrfSecret is httpOnly cookies. And I never even store them in client-side memory.
I think this is as secure as I could get. Now I'm happy with this solution.

Using Multiple Angular App and Session Management

I have 4 angular applications one is a landing app which asks user to login and has to redirect the user according to its type
to one of the other 3 applications. I am unable to figure how to should i achieve that.
Have the three apps running on different subdomains. Upon login backend send a redirect response, figuring out what type of user it is?
But this leads to cors Error. Also i am not sure whether the cookie which i am setting will be accessible in all the subdomains or not.
Is there a way out?
You can do a redirect, but it seems like an unnecessary step (and kind of convoluted for this type of application).
Instead of returning a redirect based on login, it seems more straightforward to just return the address you want to redirect to in the response. Trigger a lookup to determine which app you should be directing to (however you're doing that) and then return the address of the app in the response data. From within Angular, you can extract the address from within response.data in $http. (see angular docs). The nice thing here is you also keep routing control and knowledge of state within Angular itself.
As for the apps themselves--instead of a subdomain, you can simply put the apps into different folders on your domain. This deals with CORS and the cookie issue.
Otherwise, you'd need to set a CORS header. You would do this on whatever backend you're sending the requests to--there's usually some sort of library to make it easy, for example, Flask CORS for Flask. If you need to share cookies in this case, this StackOverflow answer discusses one way of doing it (using an intermediary domain).
Generate a security key for the user session with some TTL in an authentication table when you authenticate the user with your App1
Redirect the user to any other app in any domain with this security key where they can query the authentication table and verify the user.
Let these other applications work on their own (in the front end) and communicate with the back-end with the security key when necessary.
Lot of PHP frameworks has built-in support for this mechanism. My favorite is Silex.

Resources