As programmers, we have probably needed to implement social media login at some point and, this is an increasingly common functionality in today’s applications and websites. Social login allows users to log in to applications or websites using their accounts such as Facebook, Google, Twitter, etc. This saves users time by not having to create an additional account and password and taking advantage of the social network’s security measures. For example, if a user has enabled two-step authentication on their Google account, this security will also apply to the application or website they are using. In this tutorial, we will implement Github and login in a React application with TypeScript. We will use Vite to create our application and pnpm as package manager. Google Login with RRSS’s first steps First of all, to use the authentication services of any platform, we need to create an OAuth application and obtain the necessary keys. For GiHub follow these steps. For Google follow these steps Make sure to keep the keys in a safe place. After this, we are going to start the development of our application. Since in this application we will make use of sensitive and private data such as client secret keys, it is necessary to create a backend and use it on the server side for security reasons. So we will have two applications, backend and client, each in its directory. Setting up Backend We create our application with the following command: pnpm init -y We install the dependencies that we will need in the project: pnpm install express axios cors dotenv tsc ts-node pnpm install @types/cors @types/express @types/node ts-node-dev typescript -D After that, we create the following structure for the project: ├── src/ │ ├── routes/ │ │ ├── github-routes.ts │ │ └── google-routes.ts │ ├── controllers/ │ │ ├── github-controller.ts │ │ └── google-controller.ts │ └── server.ts ├── .env ├── .env-example Setting up Client We install the dependencies that we will need in the project: pnpm install @octokit/auth @react-oauth/google @nextui-org/react axios react-router-dom After that, we create the following structure for the project: ├── src/ │ ├── assets/ │ │ ├── icons/ │ │ │ ├── Github.tsx │ │ │ ├── Google.tsx │ │ │ ├── Logout.tsx │ │ │ └── index.ts │ ├── pages/ │ │ ├── home/ │ │ │ ├── services/ │ │ │ │ └── home-services.ts │ │ │ └── HomePage.tsx │ │ └── login/ │ │ └── LoginPage.tsx │ ├── index.tsx │ ├── App.tsx │ └── main.tsx ├── .env ├── .env-example GitHub Login Backend File : server.ts import express from 'express'; import cors from 'cors'; import githubRoutes from './routes/github-routes'; import googleRoutes from './routes/google-routes'; const PORT = process.env.PORT || 3001 const app = express(); app.use( cors({ origin: ['http://localhost:5173'], methods: 'GET,POST', }), ); app.use(express.json()); app.use('/api/github', githubRoutes); app.use('/api/google', googleRoutes); app.listen(PORT, () => console.log('Server on port', PORT)); This code creates a server application with Express. The application uses the cors library to enable cross-origin access (CORS) and sets the origin and allowed methods. The application also uses the function to enable the use of JSON in requests and to define routes for the GitHub service and the Google service. app.use() Finally, the application listens on the port specified in the constant, or on port 3001 if no port is specified. PORT File : github-routes.ts import express, { Request, Response, Router } from 'express'; import { getAccessToken, getUserData } from '../controllers/github-controller'; const router: Router = express.Router(); router.get('/accessToken', (req: Request, res: Response) => { const code = req.query.code; getAccessToken(code as string).then((resp) => res.json(resp)); }); router.get('/userData', (req: Request, res: Response) => { const accessToken = req.query.accessToken; getUserData(accessToken as string).then((resp) => res.json(resp)); }); export default router; This code creates an Express router which defines two routes y . /accessToken /userData The route s a GET route that takes a parameter from the query and calls the function of the GitHub service. /accessToken code getAccessToken The rute is a GET route that takes an parameter from the query and calls the function of the GitHub service. /userData accessToken getUserData File : google-routes.ts import express, { Request, Response, Router } from 'express'; import { getUserData } from '../controllers/google-controller'; const router: Router = express.Router(); router.get('/userData', (req: Request, res: Response) => { const accessToken = req.query.accessToken; getUserData(accessToken as string).then((resp) => res.json(resp)); }); export default router; This code creates an Express router that defines a GET route called . The route takes an parameter from the query and calls the function of the Google service. /userData accessToken getUserData File : github-controller.ts import * as dotenv from 'dotenv'; import axios from 'axios'; dotenv.config(); type AccessTokenData = { access_token: string; token_type: string; scope: string; } | null; export const getAccessToken = async ( code: string, ): Promise<AccessTokenData> => { try { const params = `?client_id=${process.env.GITHUB_CLIENT_ID}&client_secret=${process.env.GITHUB_CLIENT_SECRET}&code=${code}`; const { data } = await axios.post( `https://github.com/login/oauth/access_token${params}`, {}, { headers: { Accept: 'application/json', }, }, ); return data; } catch (error) { console.log(error); return null; } }; export const getUserData = async (accessToken: string) => { try { const { data } = await axios.get('https://api.github.com/user', { headers: { Authorization: `Bearer ${accessToken}`, }, }); return data; } catch (error) { return null; } }; The function receives the parameter makes a POST request to the GitHub API to obtain an access token using the provided , and . getAccessToken code client_id client_secret code The function receives the parameter and returns the user information from the GitHub API using the access token provided in the request. getUserData accessToken File : google-controller.ts import axios from 'axios'; export const getUserData = async (accessToken: string) => { try { const { data } = await axios.get( 'https://www.googleapis.com/oauth2/v3/userinfo', { headers: { Authorization: `Bearer ${accessToken}`, }, }, ); return data; } catch (error) { return null; } }; The function receives the parameter and performs a GET request to the Google OAuth user information API using the access token provided in the authorization header of the request and returns the user information. getUserData accessToken Now we already have our backend created with the minimum we need, now let’s go for the frontend. Client File : main.tsx import { GoogleOAuthProvider } from "@react-oauth/google" import ReactDOM from "react-dom/client" import { NextUIProvider } from "@nextui-org/react" import { darkTheme } from "./themes/darktheme" const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID import App from "./App" ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( <NextUIProvider theme={darkTheme}> <GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}> <App /> </GoogleOAuthProvider> </NextUIProvider> ) In this application we will use for the styles, you can use any UI library you want. Next UI The component provides the dark theme to all the components of the application. NextUIProvider The component provides the Google login to the application using the provided . GoogleOAuthProvider clientId Component : App.tsx import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; import { LoginPage, HomePage } from './pages'; const App = () => { return ( <Router> <Routes> <Route path="/home" element={<HomePage />}></Route> <Route path="/" element={<LoginPage />}></Route> </Routes> </Router> ); }; export default App; In the App component, we will create the routes of the pages that we will use and we will assign the corresponding component. Component LoginPage.tsx import { useGoogleLogin } from "@react-oauth/google" import { useNavigate } from "react-router-dom" import { Card, Spacer, Button, Text, Container } from "@nextui-org/react" import { IconGitHub, IconGoogle } from "../../assets/icons" const GITHUB_CLIENT_ID = import.meta.env.VITE_GITHUB_CLIENT_ID const LoginPage = () => { const navigate = useNavigate() const loginToGithub = () => { localStorage.setItem("loginWith", "GitHub") window.location.assign(`https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}`) } const loginToGoogle = useGoogleLogin({ onSuccess: tokenResponse => { localStorage.setItem("loginWith", "Google") localStorage.setItem("accessToken", tokenResponse.access_token) navigate("/home") }, }) return ( <Container display='flex' alignItems='center' justify='center' css={{ minHeight: "100vh" }}> <Card css={{ mw: "420px", p: "20px" }}> <Text size={24} weight='bold' css={{ as: "center", mb: "20px", }} > Login with </Text> <Spacer y={1} /> <Button color='gradient' auto ghost onClick={() => loginToGithub()}> <IconGitHub /> <Spacer x={0.5} /> GitHub </Button> <Spacer y={1} /> <Button color='gradient' auto ghost onClick={() => loginToGoogle()}> <IconGoogle /> <Spacer x={0.5} /> Google </Button> </Card> </Container> ) } export default LoginPage When the user clicks the GitHub login button, the user is redirected to the GitHub login page and a key is stored in the browser’s local storage indicating that the user is logging in with GitHub. When the user clicks the Google login button, the function of is used to log in with Google. If the login is successful, a key is stored in the browser’s local storage indicating that the user is signing in with Google. useGoogleLogin @react-oauth/google Component : HomePage.tsx import { useEffect, useRef, useState } from "react" import { useNavigate } from "react-router-dom" import { Button, Col, Container, Navbar, Row, Text, User } from "@nextui-org/react" import { getAccessTokenGithub, getUserDataGithub, getUserDataGoogle } from "./services/home-services" import { LogOutIcon } from "../../assets/icons" interface UserDataGithub { avatar_url: string login: string bio: string } interface UserdataGoogle { name: string picture: string email: string } const HomePage = () => { const [userDataGithub, setUserDataGithub] = useState<null | UserDataGithub>(null) const [userDataGoogle, setUserDataGoogle] = useState<null | UserdataGoogle>(null) const loginWith = useRef(localStorage.getItem("loginWith")) const navigate = useNavigate() useEffect(() => { const queryString = window.location.search const urlParams = new URLSearchParams(queryString) const codeParam = urlParams.get("code") const accessToken = localStorage.getItem("accessToken") if (codeParam && !accessToken && loginWith.current === "GitHub") { getAccessTokenGithub(codeParam).then(resp => { localStorage.setItem("accessToken", resp.access_token) getUserDataGithub(resp.access_token).then((resp: UserDataGithub) => { setUserDataGithub(resp) }) }) } else if (codeParam && accessToken && loginWith.current === "GitHub") { getUserDataGithub(accessToken).then((resp: UserDataGithub) => { localStorage.setItem("accessToken", accessToken) setUserDataGithub(resp) }) } }, [loginWith]) useEffect(() => { const accessToken = localStorage.getItem("accessToken") if (accessToken && loginWith.current === "Google") { getUserDataGoogle(accessToken).then(resp => { setUserDataGoogle(resp) }) } }, [loginWith]) const setLogOut = () => { localStorage.removeItem("accessToken") localStorage.removeItem("loginWith") navigate("/") } if (!userDataGithub && !userDataGoogle) return null return ( <> <Navbar isBordered variant='sticky'> <Navbar.Brand> <User bordered color='primary' size='lg' src={loginWith.current === "GitHub" ? userDataGithub?.avatar_url : userDataGoogle?.picture} name={loginWith.current === "GitHub" ? userDataGithub?.login : userDataGoogle?.name} description={loginWith.current === "GitHub" ? userDataGithub?.bio : userDataGoogle?.email} /> </Navbar.Brand> <Navbar.Content> <Navbar.Item> <Button auto flat size='sm' icon={<LogOutIcon fill='currentColor' />} color='primary' onClick={() => setLogOut()} > Log out </Button> </Navbar.Item> </Navbar.Content> </Navbar> <Container gap={0}> <Row gap={1}> <Col> <Text h2>Login with {loginWith.current}</Text> </Col> </Row> </Container> </> ) } export default HomePage After the user has logged in and is redirected to Home, we will use the hook to perform a series of actions when the page loads. useEffect First, we get the authorization code from the page URL and, if the user has logged in with GitHub, we get an access token and then we get the user’s data from GitHub. If the user has logged in with Google, we simply get the Google user data. In addition, we have a “Logout” button that removes the access token and login from local storage and redirects the user to the application’s home page. That’s it! Now we can log in to GitHub or Google. The next steps are to register the user and save the user information in a database or whatever suits you. The application looks something like this: Repo here. Conclusion Social login is a feature that offers many advantages for both users and developers. It is simple to integrate and can improve the user experience and security of the application or website. Therefore, it is a good idea to consider using it in your projects. Also published here.