Let’s implement a whole authentication mechanism in React with sign up, sign in, password reset, change password and sign out in TypeScript.
🐞 GitHub Repository Whole files of this tutorial are as follows:
https://github.com/morizyun/react-typescript-firebase-auth
🎳 Get Firebase App info
It will be needs for using Firebase authentication.
🐰 create-react-app with TypeScript You can install create-react-app on command line:
yarn global add create-react-app create-react-app react-ts-firebase-auth --scripts-version=react-scripts-ts cd react-ts-firebase-authyarn add firebase react-router-dom yarn add --dev @types/react-router @types/react-router-dom
After then, you can run app process by yarn start
on terminal and open http://localhost:3000
.
Also, please create some directories:
🍄 Configuration Please change tslint.json
to add as follows:
{ + "rules": { + "jsx-no-lambda": false + } }
🐮 Rebuild folders mkdir src/components mkdir src/constants mkdir src/firebase mkdir -p src/pages/Account mkdir src/pages/Admin mkdir src/pages/Home mkdir src/pages/Landing mkdir src/pages/PasswordForget mkdir src/pages/SignIn mkdir src/pages/SignUp mv src/components/App.ts src/components/App.ts rm src/logo.svg rm src/App.test.ts rm src/App.css
🚜 Constants Add src/constants/routes.ts
file:
export const SIGN_UP = "/signup" ;export const SIGN_IN = "/signin" ;export const LANDING = "/" ;export const HOME = "/home" ;export const ACCOUNT = "/account" ;export const PASSWORD_FORGET = "/password_forget" ;
🏀 Components App Create src/components/App.tsx
file:
import * as React from "react" ;import { BrowserRouter, Route, Switch } from "react-router-dom" ;import * as routes from "../constants/routes" ;import { firebase } from "../firebase" ;import { withAuthentication } from "../firebase/withAuthentication" ;import { Account } from "../pages/Account" ;import { Home } from "../pages/Home" ;import { Landing } from "../pages/Landing" ;import { PasswordForget } from "../pages/PasswordForget" ;import { SignIn } from "../pages/SignIn" ;import { SignUp } from "../pages/SignUp" ;import { Navigation } from "./Navigation" ;class AppComponent extends React.Component { constructor (props: any ) { super (props); this .state = { authUser: null }; } public componentDidMount() { firebase.auth.onAuthStateChanged(authUser => { authUser ? this .setState(() => ({ authUser })) : this .setState(() => ({ authUser: null })); }); } public render() { return (
true } path={routes.LANDING} component={Landing} /> true } path={routes.SIGN_UP} component={SignUp} /> true } path={routes.SIGN_IN} component={SignIn} /> exact={true } path={routes.PASSWORD_FORGET} component={PasswordForget} /> true } path={routes.HOME} component={Home} /> true } path={routes.ACCOUNT} component={Account} /> </Switch> div> </BrowserRouter> ); } } export const App = withAuthentication(AppComponent);
Navigation Create src/components/Navigation.tsx
file:
import * as React from "react" ;import { Link } from "react-router-dom" ;import * as routes from "../constants/routes" ;import { AuthUserContext } from "../firebase/AuthUserContext" ;import { SignOutButton } from "./SignOutButton" ;export const Navigation = () => ( {authUser => (authUser ? : )} </AuthUserContext.Consumer> ); const NavigationAuth = () => ( LandingLink> </li> HomeLink> </li> AccountLink> </li> > </li> l> ); const NavigationNonAuth = () => ( Landing</Link> li> Sign In</Link> li> </ul> );
PasswordChangeForm Create src/components/PasswordChangeForm.tsx
file:
import * as React from "react" ;import { auth } from "../firebase" ;interface InterfaceProps { error?: any ; history?: any ; passwordOne?: string ; passwordTwo?: string ; } interface InterfaceState { error?: any ; passwordOne?: string ; passwordTwo?: string ; } export class PasswordChangeForm extends React.Component< InterfaceProps, InterfaceState > { private static INITIAL_STATE = { error: null , passwordOne: "" , passwordTwo: "" }; private static propKey(propertyName: string , value: string ): object { return { [propertyName]: value }; } constructor (props: any ) { super (props); this .state = { ...PasswordChangeForm.INITIAL_STATE }; } public onSubmit = (event: any ) => { const { passwordOne }: any = this .state; auth .doPasswordUpdate(passwordOne) .then(() => { this .setState(() => ({ ...PasswordChangeForm.INITIAL_STATE })); }) .catch(error => { this .setState(PasswordChangeForm.propKey("error" , error)); }); event.preventDefault(); }; public render() { const { passwordOne, passwordTwo, error }: any = this .state; const isInvalid = passwordOne !== passwordTwo || passwordOne === "" ; return ( this .onSubmit(event)}> value={passwordOne} onChange={event => this .setStateWithEvent(event, "passwordOne" )} type ="password" placeholder="New Password" /> value={passwordTwo} onChange={event => this .setStateWithEvent(event, "passwordTwo" )} type ="password" placeholder="Confirm New Password" /> type ="submit" > Reset My Password </button> {error && {error.message}p>}
</form> ); } private setStateWithEvent(event: any, columnType: string): void { this.setState( PasswordChangeForm.propKey(columnType, (event.target as any).value) ); } }
SignOutButton Create src/components/SignOutButton.tsx
file:
import * as React from "react" ;import { auth } from "../firebase" ;export const SignOutButton = () => ( type ="button" onClick={auth.doSignOut}> Sign Out </button> );
🎉 firebase auth Create src/firebase/auth.tsx
file:
import { auth } from "./firebase" ;export const doCreateUserWithEmailAndPassword = ( email: string , password: string ) => auth.createUserWithEmailAndPassword(email, password); export const doSignInWithEmailAndPassword = (email: string , password: string ) => auth.signInWithEmailAndPassword(email, password); export const doSignOut = () => auth.signOut();export const doPasswordReset = (email: string ) => auth.sendPasswordResetEmail(email); export const doPasswordUpdate = async (password: string ) => { if (auth.currentUser) { await auth.currentUser.updatePassword(password); } throw Error ("No auth.currentUser!" ); };
AuthUserContext Create src/firebase/AuthUserContext.ts
file:
import * as React from "react" ;export const AuthUserContext = React.createContext(null );
db Create src/firebase/db.ts
file:
import { db } from "./firebase" ;export const doCreateUser = (id: string , username: string , email: string ) => db.ref(`users/${id} ` ).set({ email, username }); export const onceGetUsers = () => db.ref("users" ).once("value" );
firebase Create src/firebase/firebase.ts
file:
import * as firebase from "firebase/app" ;import "firebase/auth" ;import "firebase/database" ;const config = { apiKey: "YOUR_API_KEY" , authDomain: "YOUR_AUTH_DOMAIN" , databaseURL: "YOUR_DATABASE_URL" , messagingSenderId: "YOUR_MESSAGING_SENDER_ID" , projectId: "YOUR PROJECT_ID" , storageBucket: "YOUR_STORAGE_BUCKET" }; if (!firebase.apps.length) { firebase.initializeApp(config); } export const auth = firebase.auth();export const db = firebase.database();
index Create src/firebase/index.ts
file:
import * as auth from "./auth" ;import * as db from "./db" ;import * as firebase from "./firebase" ;export { auth, db, firebase };
withAuthentication Create src/firebase/withAuthentication.tsx
file:
import * as React from "react" ;import { firebase } from "../firebase" ;import { AuthUserContext } from "./AuthUserContext" ;interface InterfaceProps { authUser?: any ; } interface InterfaceState { authUser?: any ; } export const withAuthentication = (Component: any ) => { class WithAuthentication extends React.Component< InterfaceProps, InterfaceState > { constructor (props: any ) { super (props); this .state = { authUser: null }; } public componentDidMount() { firebase.auth.onAuthStateChanged(authUser => { authUser ? this .setState(() => ({ authUser })) : this .setState(() => ({ authUser: null })); }); } public render() { const { authUser } = this .state; return ( </AuthUserContext.Provider> ); } } return WithAuthentication; };
withAuthorization Create src/firebase/withAuthorization.tsx
fle:
import * as React from "react" ;import { withRouter } from "react-router-dom" ;import * as routes from "../constants/routes" ;import { firebase } from "../firebase" ;import { AuthUserContext } from "./AuthUserContext" ;interface InterfaceProps { history?: any ; } export const withAuthorization = (condition: any ) => (Component: any ) => { class WithAuthorization extends React.Component { public componentDidMount() { firebase.auth.onAuthStateChanged(authUser => { if (!condition(authUser)) { this .props.history.push(routes.SIGN_IN); } }); } public render() { return ( {authUser => (authUser ? : null )} </AuthUserContext.Consumer> ); } } return withRouter(WithAuthorization as any); };
🍮 Pages Account Create src/pages/Account/index.tsx
file:
import * as React from "react" ;import { PasswordChangeForm } from "./PasswordChangeForm" ;import { AuthUserContext } from "../../firebase/AuthUserContext" ;import { withAuthorization } from "../../firebase/withAuthorization" ;import { PasswordForgetForm } from "../PasswordForget/PasswordForgetForm" ;export const AccountComponent = () => ( {authUser => (
Account: {(authUser as any ).email}</h1> > </div> )} AuthUserContext.Consumer> ); const authCondition = (authUser: any ) => !!authUser;export const Account = withAuthorization(authCondition)(AccountComponent);
Create src/pages/Account/PasswordChangeForm.tsx
file:
import * as React from "react" ;import { auth } from "../../firebase" ;interface InterfaceProps { error?: any ; history?: any ; passwordOne?: string ; passwordTwo?: string ; } interface InterfaceState { error?: any ; passwordOne?: string ; passwordTwo?: string ; } export class PasswordChangeForm extends React.Component< InterfaceProps, InterfaceState > { private static INITIAL_STATE = { error: null , passwordOne: "" , passwordTwo: "" }; private static propKey(propertyName: string , value: string ): object { return { [propertyName]: value }; } constructor (props: any ) { super (props); this .state = { ...PasswordChangeForm.INITIAL_STATE }; } public onSubmit = (event: any ) => { const { passwordOne }: any = this .state; auth .doPasswordUpdate(passwordOne) .then(() => { this .setState(() => ({ ...PasswordChangeForm.INITIAL_STATE })); }) .catch(error => { this .setState(PasswordChangeForm.propKey("error" , error)); }); event.preventDefault(); }; public render() { const { passwordOne, passwordTwo, error }: any = this .state; const isInvalid = passwordOne !== passwordTwo || passwordOne === "" ; return ( this .onSubmit(event)}> value={passwordOne} onChange={event => this .setStateWithEvent(event, "passwordOne" )} type ="password" placeholder="New Password" /> value={passwordTwo} onChange={event => this .setStateWithEvent(event, "passwordTwo" )} type ="password" placeholder="Confirm New Password" /> type ="submit" > Reset My Password </button> {error && {error.message}p>}
</form> ); } private setStateWithEvent(event: any, columnType: string): void { this.setState( PasswordChangeForm.propKey(columnType, (event.target as any).value) ); } }
Home Create src/pages/Home/index.tsx
file:
import * as React from "react" ;import { db } from "../../firebase" ;import { withAuthorization } from "../../firebase/withAuthorization" ;import { UserList } from "./UserList" ;class HomeComponent extends React.Component { constructor (props: any ) { super (props); this .state = { users: null }; } public componentDidMount() { db.onceGetUsers().then(snapshot => this .setState(() => ({ users: snapshot.val() })) ); } public render() { const { users }: any = this .state; return (
Home Page</h2> The Home Page is accessible by every signed in user.p>
{!!users && } </div> ); } } const authCondition = (authUser: any) => !!authUser; export const Home = withAuthorization(authCondition)(HomeComponent);
Create src/pages/Home/UserList.tsx
file:
import * as React from "react" ;interface InterfaceProps { users?: any ; } export class UserList extends React.Component { constructor (props: any ) { super (props); } public render() { const { users }: any = this .props; return (
List of User name</h2> (Saved on Sign Up in Firebase Database)p>
{Object .keys(users).map(key => { return {users[key].username}</li>; })} l> </div> ); } }
Landing Create src/pages/Landing/index.tsx
file:
import * as React from "react" ;export const Landing = () => { return (
Landing Page</h2> div> ); };
PasswordForget Create src/pages/PasswordForget/index.tsx
file:
import * as React from "react" ;import { Link } from "react-router-dom" ;import { PasswordForgetForm } from "./PasswordForgetForm" ;export const PasswordForget = () => (
PasswordForget</h1> > </div> ); export const PasswordForgetLink = () => (
">Forgot Password
);
Create src/pages/PasswordForget/PasswordForgetForm.tsx
file:
SignIn Create src/pages/SignIn/index.tsx
file:
import * as React from "react" ;import { withRouter } from "react-router-dom" ;import { PasswordForgetLink } from "../PasswordForget" ;import { SignUpLink } from "../SignUp" ;import { SignInForm } from "./SignInForm" ;const SignInComponent = ({ history }: { [key: string ]: any } ) => (
SignIn</h1> > </div> ); export const SignIn = withRouter(SignInComponent);
Create src/pages/SignIn/SignInForm.tsx
file:
import * as React from "react" ;import * as routes from "../../constants/routes" ;import { auth } from "../../firebase" ;interface InterfaceProps { email?: string ; error?: any ; history?: any ; password?: string ; } interface InterfaceState { email: string ; error: any ; password: string ; } export class SignInForm extends React.Component< InterfaceProps, InterfaceState > { private static INITIAL_STATE = { email: "" , error: null , password: "" }; private static propKey(propertyName: string , value: any ): object { return { [propertyName]: value }; } constructor (props: InterfaceProps ) { super (props); this .state = { ...SignInForm.INITIAL_STATE }; } public onSubmit = (event: any ) => { const { email, password } = this .state; const { history } = this .props; auth .doSignInWithEmailAndPassword(email, password) .then(() => { this .setState(() => ({ ...SignInForm.INITIAL_STATE })); history.push(routes.HOME); }) .catch(error => { this .setState(SignInForm.propKey("error" , error)); }); event.preventDefault(); }; public render() { const { email, password, error } = this .state; const isInvalid = password === "" || email === "" ; return ( this .onSubmit(event)}> value={email} onChange={event => this .setStateWithEvent(event, "email" )} type ="text" placeholder="Email Address" /> value={password} onChange={event => this .setStateWithEvent(event, "password" )} type ="password" placeholder="Password" /> type ="submit" > Sign In </button> {error && {error.message}p>}
</form> ); } private setStateWithEvent(event: any, columnType: string): void { this.setState(SignInForm.propKey(columnType, (event.target as any).value)); } }
After then, please confirm http://localhost:3000
again. You can see some pages for sign up, sign in, password reset, change password and sign out.
🗽 GitHub Repository Whole files of this tutorial are as follows:
https://github.com/morizyun/react-typescript-firebase-auth
😀 References
🖥 Recommended VPS Service
VULTR provides high performance cloud compute environment for you.
Vultr has 15 data-centers strategically placed around the globe, you can use a VPS with 512 MB memory for just $ 2.5 / month ($ 0.004 / hour).
In addition, Vultr is up to 4 times faster than the competition, so please check it => Check Benchmark Results !!