Skip to content

Commit

Permalink
Merge pull request #62 from frontendcafe/issue-49-auth-firestore
Browse files Browse the repository at this point in the history
[MAIN][FEATURE] Issue 49 auth firestore in server side, refreshtoken
  • Loading branch information
kevin-dev71 authored Aug 23, 2022
2 parents bf02eb9 + 4353ed9 commit 150688d
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# firebase admin SDK private key
privateKey.json
17 changes: 17 additions & 0 deletions firebaseAdminConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import admin, { ServiceAccount } from "firebase-admin";

import serviceAccount from "@/privateKey.json";

export const verifyIdToken = (token: string) => {
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as ServiceAccount),
});
}
return admin
.auth()
.verifyIdToken(token)
.catch((error: any) => {
throw error;
});
};
2 changes: 1 addition & 1 deletion modules/Auth/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Form: React.FC<FormProps> = ({ title, onSubmit, children, submitLabel }) =

<form onSubmit={onSubmit}>
{children}
<Button>{submitLabel}</Button>
<Button type="submit">{submitLabel}</Button>
</form>
</div>
);
Expand Down
65 changes: 65 additions & 0 deletions modules/Auth/context/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
import type { User, UserCredential } from "firebase/auth";
import nookies from "nookies";

import { auth } from "@/firebaseConfig";

interface AuthProviderProps {
children?: React.ReactNode;
}

interface AuthContextType {
user: User | UserCredential | null;
setUser: (user: User | UserCredential | null) => void;
}

const AuthContext = createContext<AuthContextType>({
user: null,
setUser: () => {},
});

const refreshTokenMinutes = 10 * 60 * 1000;

export const AuthProvider = ({ children }: AuthProviderProps) => {
const [user, setUser] = useState<User | UserCredential | null>(null);

useEffect(() => {
if (typeof window !== "undefined") {
(window as any).nookies = nookies;
}
return auth.onIdTokenChanged(async (loggedUser) => {
if (!loggedUser) {
setUser(null);
nookies.destroy(null, "token");
nookies.set(null, "token", "", { path: "/" });
return;
}

const token = await loggedUser.getIdToken();
setUser(loggedUser);
nookies.destroy(null, "token");
nookies.set(null, "token", token, { path: "/" });
});
}, []);

// force refresh the token every 10 minutes
useEffect(() => {
const handleRefreshToken = setInterval(async () => {
const updatedUser = auth.currentUser;
if (updatedUser) await updatedUser.getIdToken(true);
}, refreshTokenMinutes);
return () => {
return clearInterval(handleRefreshToken);
};
}, []);

const value = useMemo(() => {
return { user, setUser };
}, [user]);

return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
return useContext(AuthContext);
};
9 changes: 8 additions & 1 deletion modules/shared/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import Router from "next/router";

import { auth } from "@/firebaseConfig";
import { useAuth } from "@/modules/Auth/context/AuthProvider";

interface AuthGuardProps {
children: React.ReactNode;
Expand All @@ -16,17 +17,23 @@ export const AuthGuard: React.FC<AuthGuardProps> = ({
}: AuthGuardProps) => {
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const { setUser } = useAuth();

useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
if (user) {
setUser(user);
}
setIsLoading(false);
setIsAuthenticated(!!user);
});

return () => {
unsubscribe();
};
}, []);
}, [setUser]);

// check if token is expired or not, and set it to cookies

if (isLoading) {
return null;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
"@emotion/styled": "^11.10.0",
"@fontsource/work-sans": "^4.5.11",
"firebase": "^9.9.2",
"firebase-admin": "^11.0.1",
"framer-motion": "^6.5.1",
"next": "12.2.4",
"nookies": "^2.5.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"yup": "^0.32.11"
Expand Down
9 changes: 6 additions & 3 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextComponentType, NextPageContext } from "next";

import { AuthProvider } from "@/modules/Auth/context/AuthProvider";
import { AuthGuard } from "@/modules/shared/AuthGuard";
import { theme } from "@/modules/shared/theme";
import { ChakraProvider } from "@chakra-ui/react";
Expand Down Expand Up @@ -29,9 +30,11 @@ const MyApp = ({ Component, pageProps }: AppProps) => {

return (
<ChakraProvider theme={theme}>
<AuthGuard redirectUrl={redirectUrl} authenticationType={authenticationType}>
<Component {...pageProps} />
</AuthGuard>
<AuthProvider>
<AuthGuard redirectUrl={redirectUrl} authenticationType={authenticationType}>
<Component {...pageProps} />
</AuthGuard>
</AuthProvider>
</ChakraProvider>
);
};
Expand Down
22 changes: 21 additions & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import type { NextPage } from "next";
import type { GetServerSideProps, NextPage } from "next";
import Head from "next/head";
import nookies from "nookies";

import { verifyIdToken } from "@/firebaseAdminConfig";

const Home: NextPage & { requiresAuthentication: boolean } = () => {
return (
Expand All @@ -17,6 +20,23 @@ const Home: NextPage & { requiresAuthentication: boolean } = () => {
);
};

export const getServerSideProps: GetServerSideProps = async (context) => {
try {
const cookies = nookies.get(context);
const token = await verifyIdToken(cookies.token);
const { uid } = token;
// eslint-disable-next-line no-console
console.log({ uid });
} catch (error: any) {
// TODO
}
return {
props: {
requiresAuthentication: true,
},
};
};

Home.requiresAuthentication = true;

export default Home;
6 changes: 4 additions & 2 deletions pages/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React, { ChangeEventHandler, FormEventHandler, useState } from "react";
import { NextPage } from "next";

import FormField from "@/modules/Auth/components/FormField";
import { useAuth } from "@/modules/Auth/context/AuthProvider";
import { logIn } from "@/modules/Auth/firebase/auth";
import Form from "@/modules/Auth/Form";

const LoginPage: NextPage & { redirectIfAuthenticated: boolean } = () => {
const { setUser } = useAuth();
const [state, setState] = useState({
email: "",
password: "",
Expand All @@ -31,11 +33,11 @@ const LoginPage: NextPage & { redirectIfAuthenticated: boolean } = () => {
if (user) {
// eslint-disable-next-line no-alert
window.alert("User logged");
// eslint-disable-next-line no-console
console.log(user);
setUser(user);
} else {
// eslint-disable-next-line no-alert
window.alert("Unable to login");
setUser(null);
}
};

Expand Down
5 changes: 3 additions & 2 deletions pages/register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import React, { ChangeEventHandler, FormEventHandler, useState } from "react";
import { NextPage } from "next";

import FormField from "@/modules/Auth/components/FormField";
import { useAuth } from "@/modules/Auth/context/AuthProvider";
import { signUp } from "@/modules/Auth/firebase/auth";
import Form from "@/modules/Auth/Form";

const Register: NextPage & { redirectIfAuthenticated: boolean } = () => {
const { setUser } = useAuth();
const [state, setState] = useState({
email: "",
password: "",
Expand Down Expand Up @@ -39,8 +41,7 @@ const Register: NextPage & { redirectIfAuthenticated: boolean } = () => {
if (user) {
// eslint-disable-next-line no-alert
window.alert("User created");
// eslint-disable-next-line no-console
console.log(user);
setUser(user);
} else {
// eslint-disable-next-line no-alert
window.alert("User not created");
Expand Down

0 comments on commit 150688d

Please sign in to comment.