I am building an app in react native with an AWS backend. I am using aws cognito through amplify to manage authentication in the app.
I am trying to integrate the ability to check whether a user is already authenticated so that they don't have to manually sign in every time they open the app.
When I use Auth.currentAuthenticatedUser() or Auth.currentSession() to check the user is authenticated, they return nothing.
NOTE: when use the Auth.signIn() method, the user object it returns, returns a session.
I have configured amplify to use my pre-existing cognito user pool with the following configuration.
import { Amplify } from "aws-amplify";
async function amplifySetup() {
Amplify.configure({
Auth: {
identityPoolId: 'REGION:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
region: "REGION",
identityPoolRegion: "REGION,
userPoolId: "REGION_XXXXXXXX",
userPoolWebClientId: "XXXXXXXXXXXXXXXXXXXXXX",
},
});
}
export default amplifySetup;
I have then run this command at the start of the App function in my App.js file. When I sign a user in, this returns a cognito user successfully. When I use Auth.currentAuthenticatedUser() or Auth.currentSession() to check the user is authenticated, they return nothing. I have no idea what to do or what avenue to take next.
You need to share more code and details. If you have your own UI then you'll need to await those APIs and call them from a hook.
If you're using Amplify's auth UI elements, then they'll take care of themselves, for the most part. You just wrap <App /> in <Authenticator.Provider> to provide context.
Here's an App.tsx example that uses the Amplify UI elements and react-router-v6. It calls useAuthenticator to determine whether to show the marketing screen(s) or signed-in user screens.
Other parts of the example are in this gist.
import { Authenticator, View, Image, Text, Heading, useTheme, useAuthenticator } from '#aws-amplify/ui-react'
import { BrowserRouter } from "react-router-dom"
import { Routes, Route } from "react-router-dom"
import AppRoutes from "./AppRoutes"
import MarketingPage from './routes/MarketingPage'
import logoSvg from './logo.svg'
function App() {
const { route, user, signOut } = useAuthenticator(context => [context.route, context.user, context.isPending])
const UnauthenticatedUserRoutes = () =>
<Routes>
{/* Wrap marketing page in a hidden authenticator to work around a bug. */}
<Route index element={<><Authenticator className='hidden' /> <MarketingPage /></>} />
{/* <Route index element={<MarketingPage />} /> */}
<Route path="*" element={
<Authenticator
className='flex flex-col items-center justify-center w-screen h-screen bg-slate-50 min-w-max' />
} />
</Routes >
return route === 'authenticated' ? <AppRoutes user={user} signOut={signOut} /> : <UnauthenticatedUserRoutes />
}
const AppWithRouter = () => <BrowserRouter><App /></BrowserRouter>
export default AppWithRouter
Related
I am new to the concept of authentications in apps, especially using tokens. I found the react-auth-kit library to help me do the authentication. I have a simple login using a username and a password with a set backend that works well on Postman. I managed to also authenticate the user into the dashboard, but when I reload the page, the user is sent back to the login page.
I tried using sessionStorage which someone pointed out as a security risk in a blog and didn't succeed either. I did not see the concept in the documentation. Could someone point me in the right direction, maybe a better library or a workaround on this one?
// In my app component...
import Login from "./components/Login";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Dashboard from "./pages/Dashboard";
import { useIsAuthenticated } from 'react-auth-kit'
import { useEffect, useState } from "react";
function App() {
const redirect = () => {
if (isAuthState) {
return <Dashboard />
} else {
return <Login />
}
}
return (
<BrowserRouter>
{/* <Login /> */}
<Routes>
<Route path='/' element={ <Login /> } />
<Route path='/Dashboard' element={redirect()} />
</Routes>
</BrowserRouter>
);
}
// In my Login component this is the handler for the form. I used react-hook-form for validation...
const signIn = useSignIn()
const navigate = useNavigate()
const login: SubmitHandler<Inputs> = (data) => {
axios.post<SignInType>('http://127.0.0.1:8000/api/login', data)
.then((res) => {
if(res.data.status === 200) {
if(signIn({token: res.data.token, tokenType: 'Bearer', expiresIn: 300000})) {
navigate('/dashboard')
}
} else {
setCredentialsError('Invalid credentials, please try again...')
}
})
};
My app's routes are working fine. All the front-end routes you will see below render as intended.
However, if you go to the app and type /tournaments or /users it will display the raw json because those are two paths I defined in the backend Express routes for some of the data stuff.
So, I found a solution using Redirect via react-router-dom, and it works:
ValidateRoute.js
import React from 'react';
import { Redirect, Route } from 'react-router-dom';
const ValidateRoute = props => {
if(props.type === "invalid") return <Redirect to="/" />;
else return <Route {...props} />
};
export default ValidateRoute;
App.js
import React, { Component } from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Container, Row, Col } from 'reactstrap';
import { Provider } from 'react-redux';
import store from './store';
import NavigationBar from './components/layout/NavigationBar';
import ProfileSidebar from './components/layout/ProfileSidebar';
import TournamentIndex from './components/tournaments/Index';
import TournamentShow from './components/tournaments/Show';
import TournamentStart from './components/tournaments/Start';
import PlayerProfile from './components/players/PlayerProfile';
import PlayerDirectory from './components/players/PlayerDirectory';
import News from './components/news/SmashNews';
import { loadUser } from './actions/authActions';
import ValidateRoute from './components/ValidateRoute';
export default class App extends Component{
componentDidMount() {
store.dispatch(loadUser());
};
render() {
return (
<Provider store={store}>
<Router>
<NavigationBar />
<Container>
<Row className="justify-content-sm-center justify-content-md-center">
<Col sm="7" md="7" lg="5" xl="5">
<ProfileSidebar />
</Col>
<Col sm="7" md="7" lg="7" xl="7">
<Switch>
<Route exact path="/" component={TournamentIndex} />
<Route path="/tournaments/:id" component={TournamentShow} />
<Route path="/tournaments/:id/start" component={TournamentStart} />
<Route path="/players" component={PlayerDirectory} />
<Route path="/player/:id" component={PlayerProfile} />
<Route path="/smash-news" component={News} />
<ValidateRoute path="/tournaments" type="invalid"/>
<ValidateRoute path="/users" type="invalid"/>
</Switch>
</Col>
</Row>
</Container>
</Router>
</Provider>
);
};
};
With that, when I try /tournaments or /users, it redirects to main path "/" on localhost only.
On heroku however, it still just displays the backend json and no Redirect takes place.
I can't find any solution about this, only similar issues that don't exactly fit this scenario. Anyone got an insight? Thanks
Try to use this:
history.push(‘/‘)
Method by bringing history from react-router-dom
my answer is going to be a bit different than expected as this is going to be a change in the backend code and not in the frontend as you said that:
However, if you go to the app and type /tournaments or /users it will display the raw json because those are two paths I defined in the backend Express routes for some of the data stuff.
So I think what would be the best solution is not trying to use Redirect as a fix but rather put add /api/v1/[route name] to all of your routes, for example:
const express = require("express");
const router = express.Router();
// Import Routes
const usersRoute = require("./users");
const jobsRoute = require("./jobs");
const utilRoute = require("./utils");
const betsRoute = require("./bets");
// Routes
router.use("/api/v1/users", usersRoute);
router.use("/api/v1/jobs", jobsRoute);
router.use("/api/v1/utils", utilRoute);
router.use("/api/v1/bets", betsRoute);
Once you have done so, you could add a proxy on your frontend for each request that starts with /api/ it will proxy it to the server address based on production/development mode which when run locally will be localhost:PORT and production will be something else.
to proxy the request you would need the 'http-proxy-middleware' and it can be used as such:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/api',
createProxyMiddleware({
target: `${process.env.REACT_APP_API_ENDPOINT}`,
changeOrigin: true,
}),
);
};
process.env.REACT_APP_API_ENDPOINT will be your api endpoint which you will store inside an env file. based on the NODE_ENV you could use different env files based on which script you run.
I know this is a long answer, but this will make your code much cleaner and no future issues will arise from using the same endpoints for the frontend or backend.
I am fairly new to React and trying to implement Single Sign On Authentication in my React App.
Objectives:
Provide a login page where the user can enter their email address
On click of Sign-in user get the SSO popup (based Azure AD) to accept the terms and sign-in
Call graph API to retrieve user details (email ID, etc.)
Retrieve the sign in token and store in browser cache (localStorage) and use it for subsequent URL accesses (React routes).
I have come across MSAL (https://github.com/AzureAD/microsoft-authentication-library-for-js) which seems to be useful for this.
What I have tried:
Based on the MSDN docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa, I have registered my React SPA app in the Azure and got the client ID.
I have created a single js file (Auth.js) to handle sign-in, token generation and graph API call as mentioned in the docs: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-javascript-spa#use-the-microsoft-authentication-library-msal-to-sign-in-the-user
In my index.js I have configured the routes:
ReactDOM.render(<MuiThemeProvider theme={theme}>
<Router>
<Switch>
<Route path="/" exact component={Projects}/>
<Route path="/projects" exact component={Projects}/>
<Route path="/admin" exact component={Admin}/>
<Route path="/projectstages/:projectNumber" exact component={ProjectStages}/>
<Route path="/formspage" exact component={FormsPage}/>
<Route path="/users" exact component={UserManagement}/>
</Switch>
</Router>
</MuiThemeProvider>, document.getElementById('root'));
These routes (components) gets rendered within the main App.jsx component:
class App extends Component {
render(){
return(
<div className="App">
{this.props.children}
</div>
);
}
}
How do I integrate this within my React app so that only authenticated users can access the React routes along with the objectives I mentioned above? Please let me know if I can provide more details or explain more about this.
This is usually achieved using higher-order-components.
The idea is, when you load a page that requires authentication, you call an api to get authentication using access token stored from your cookies or whatever storage you use. Then you need to wrap your protected routes to a HOC that checks the authentication data.
import React, {useState, useContext, useRef, createContext } from 'react'
const AuthContext = createContext(null)
export const withAuth = (requireAuth = true) => (WrappedComponent) => {
function Auth(props) {
const isMounted = useRef(false);
// this is the authentication data i passed from parent component
// im just using
const { loading, error, auth } = useContext(AuthContext);
useEffect(() => {
isMounted.current = true;
}, []);
if (!isMounted.current && loading && requireAuth !== 'optional') {
return (<span>Loading...</span>);
}
if ((!auth || error) && requireAuth === true) {
return (<Redirect to="/login" />);
} if (auth && requireAuth === false) {
return (<Redirect to="/" />);
}
return (
<WrappedComponent {...props} />
);
}
return Auth;
};
export function AuthenticationProvider(props) {
const [auth, setAuth] = useState()
const [error, setErr] = usetState()
const [loading, setLoading] = useState(true)
useEffect(() => {
// get authentication here
api.call('/auth')
.then(data => {
setAuth(data)
setLoading(false)
})
.catch(err => {
setLoading(false)
setErr(err)
})
})
return (
<AuthContext.Provider value={{ auth, error, loading }}>
{children}
</AuthContext.Provider>
)
}
Then you can wrap your App with the Authentication Provider
<AuthenticationProvider>
<App/>
</AuthenticationProvider>
And for each of the pages, you use the HOC like this
function ProtectedPage(props){
// your code here
}
export default withAuth(true)(ProtectedPage)
I'd like to recommend to use package for this:
https://www.npmjs.com/package/react-microsoft-login
Install:
yarn add react-microsoft-login
# or
npm i react-microsoft-login
Import and configure component:
import React from "react";
import MicrosoftLogin from "react-microsoft-login";
export default props => {
const authHandler = (err, data) => {
console.log(err, data);
};
return (
<MicrosoftLogin clientId={YOUR_CLIENT_ID} authCallback={authHandler} />
);
};
I have followed this tutorial to implement authentication in my gatsby project. The problem is I have first setup the project and the routing is made from the pages folder and then I have implemented the above auth code but it still taking the routes from the pages folder and not from the app.js file. Could someone please help how can I route my components from the app.js instead of using from pages folder.
This is my gatsby-nodejs file
// Implement the Gatsby API “onCreatePage”. This is
// called after every page is created.
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/app/)) {
page.matchPath = "/app/*"
// Update the page.
createPage(page)
}
}
here is src/pages.app.js
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/ddddddddddddddddddd" component={Home} />
<Login path="/" />
</Router>
</Layout>
)
export default App
The paths that you have in your App.js should have /app/ prepended in front of them since your PrivateRoute logic uses that to check for a login. Furthermore what your gatsby-node.js file is really saying is that for routes starting with app it should create a new page. Your src/pages/app.js has the task to define how these pages should be created (since they won't be the usual generated static pages by gatsby)
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/layout"
import Home from '../components/dashboard/home/container'
import Login from '../components/marketing/home/pulsemetrics'
import { isLoggedIn } from "../services/auth"
console.log('vvvvvvvvvvvvvvvvvvvvv')
const PrivateRoute = ({ component: Component, location, ...rest }) => {
console.log('hjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiii')
if (!isLoggedIn() && location.pathname !== `/app/login`) {
// If the user is not logged in, redirect to the login page.
navigate(`/app/login`)
return null
}
return <Component {...rest} />
}
const App = () => (
<Layout>
<Router>
<PrivateRoute path="/app/home" component={Home} />
<Login path="/app/login" />
</Router>
</Layout>
)
export default App
Read the gatsby client-only routes documentation for reference or have a look at this github issue
My Routes.js
<Route path="/game-center" component={GameCenter} />
<Route path="/game-center/pickAndWin" component={PickAndWin} />
<Route path="/game-center/memory" component={Memory} />
<Route path="/game-center/summary" component={GameSummary} />
</Route>
</Router>
On Card Click i'm routing him to the game or summary depending whther game is live or expired.
cardClick=(type, name, status, gameId) => {
console.log(`here${type}${status}`, name);
this.props.dispatch(GameCenterActions.setShowGame());
if (status === LIVE) {
this.props.dispatch(GameCenterActions.selectGame({ type, name, status, gameId }));
this.props.dispatch(GameCenterActions.resetShowSummary());
hashHistory.push(LIVE_GAMES[type]);
} else if (status === EXPIRED) {
this.props.dispatch(GameCenterActions.setShowSummary());
console.log(`${EXPIRED_GAMES}summary page here`);
this.props.dispatch(GameCenterActions.selectGame({ type, name, status, gameId }));
hashHistory.push('/game-center/summary');
}
}
When user directly types url '/game-center/summary' he should not be allowed and should be sent back to home page.
Is this possible in react router itself? I want to implement this in my entire app.
I don't want user to directly navigate to pages by typing urls but going to pages only using links inside the app.
You can do this by using Higher Order Components.
Such as you can set a flag when user is authenticated and then attach this HOC with the specified component in react router
import React,{Component} from 'react';
import {connect} from 'react-redux';
export default function(ComposedComponent){
class Authentication extends Component{
static contextTypes = {
router : React.PropTypes.object
}
componentWillMount(){
if(!this.props.user){
this.context.router.push('/');
}
}
componentWillUpdate(nextProps){
if(!nextProps.user){
this.context.router.push('/');
}
}
render(){
return(<ComposedComponent {...this.props}/>);
}
}
}
And then in your routes
<Route path="home" component={requireAuth(Home)}></Route>