我遇到一個場景,該場景須要使用Create-React-App在React 應用中實現「使用Github登陸」功能。雖然這聽起來很簡單,但在嘗試作這件事時,你可能會遇到一些麻煩。所以,本文的目的是提供一個指南,幫助你在你的應用程序中實現這樣的功能。讓咱們如今就開始吧!css
按照此處提供的步驟登陸到你的Github賬戶並建立OAuth應用。注意:對於本例,在建立OAuth應用時,若是你在本地運行應用,能夠將主頁URL設置爲http://localhost:3000/,將受權回調URL設置爲http://localhost:3000/login。在根目錄下建立一個 .env
文件,並設置這些變量:node
REACT_APP_CLIENT_ID=你的Client ID REACT_APP_CLIENT_SECRET=你的Client Secret REACT_APP_REDIRECT_URI=http://localhost:3000/login
繼續使用你的首選來建立你的react應用程序,在這個例子中,咱們將使用Create-React-App。若是你使用這種模式,你必須刪除index.css、App.css、App.test.js和serviceWorker.js等文件。編輯index.js,確保它看起來像這樣:react
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));
另外,編輯App.js,確保它看起來像這樣:git
import React, { createContext, useReducer } from 'react'; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import Home from "./components/Home"; import Login from "./components/Login"; import { initialState, reducer } from "./store/reducer"; export const AuthContext = createContext(); function App() { const [state, dispatch] = useReducer(reducer, initialState); return ( <AuthContext.Provider value={{ state, dispatch }} > <Router> <Switch> <Route path="/login" component={Login}/> <Route path="/" component={Home}/> </Switch> </Router> </AuthContext.Provider> ); } export default App;
在App.js文件中,導入2個組件(Home.js 和 Login.js )。要建立這2個組件,進入src文件夾,建立一個名爲component的文件夾,裏面有2個文件(Home.js和Login.js)。在根文件夾中,你能夠在下面運行此命令來建立它們。github
mkdir -p src/components && cd src/components && touch Home.js Login.js
接下來,你會觀察到,咱們從store導入了狀態和reducer,繼續並設置一個簡單的store,它將保持應用程序狀態。要作到這一點,導航到src文件夾中,並建立一個名爲store的文件夾,在它裏面建立一個名爲reducer的子文件夾,並在reducer文件夾裏面建立一個index.js文件。在根目錄下,你能夠運行下面這個命令來建立它們。shell
mkdir -p src/store/reducer && cd src/store/reducer && touch index.js
Store中index.js文件的內容應以下所示。express
export const initialState = { isLoggedIn: JSON.parse(localStorage.getItem("isLoggedIn")) || false, user: JSON.parse(localStorage.getItem("user")) || null, client_id: process.env.REACT_APP_CLIENT_ID, redirect_uri: process.env.REACT_APP_REDIRECT_URI, client_secret: process.env.REACT_APP_CLIENT_SECRET, proxy_url: process.env.REACT_APP_PROXY_URL }; export const reducer = (state, action) => { switch (action.type) { case "LOGIN": { localStorage.setItem("isLoggedIn", JSON.stringify(action.payload.isLoggedIn)) localStorage.setItem("user", JSON.stringify(action.payload.user)) console.log(action.payload.isLoggedIn) return { ...state, isLoggedIn: action.payload.isLoggedIn, user: action.payload.user }; } case "LOGOUT": { localStorage.clear() return { ...state, isLoggedIn: false, user: null }; } default: return state; } };
它包含InitialState對象和一個reducer函數,該函數包含派發的動做以突變狀態。json
這時,咱們就能夠在咱們的組件上下功夫了。讓咱們在Login.js上工做,這將是一個簡單的組件,它有一個按鈕,能夠觸發Github API的登陸請求。api
import React, { useState, useEffect, useContext } from "react"; import { Redirect } from "react-router-dom"; import Styled from "styled-components"; import GithubIcon from "mdi-react/GithubIcon"; import { AuthContext } from "../App"; export default function Login() { const { state, dispatch } = useContext(AuthContext); const [data, setData] = useState({ errorMessage: "", isLoading: false }); const { client_id, redirect_uri } = state; useEffect(() => { // After requesting Github access, Github redirects back to your app with a code parameter const url = window.location.href; const hasCode = url.includes("?code="); // If Github API returns the code parameter if (hasCode) { const newUrl = url.split("?code="); window.history.pushState({}, null, newUrl[0]); setData({ ...data, isLoading: true }); const requestData = { client_id: state.client_id, redirect_uri: state.redirect_uri, client_secret: state.client_secret, code: newUrl[1] }; const proxy_url = state.proxy_url; // Use code parameter and other parameters to make POST request to proxy_server fetch(proxy_url, { method: "POST", body: JSON.stringify(requestData) }) .then(response => response.json()) .then(data => { dispatch({ type: "LOGIN", payload: { user: data, isLoggedIn: true } }); }) .catch(error => { setData({ isLoading: false, errorMessage: "Sorry! Login failed" }); }); } }, [state, dispatch, data]); if (state.isLoggedIn) { return <Redirect to="/" />; } return ( <Wrapper> <section className="container"> <div> <h1>Welcome</h1> <span>Super amazing app</span> <span>{data.errorMessage}</span> <div className="login-container"> {data.isLoading ? ( <div className="loader-container"> <div className="loader"></div> </div> ) : ( <> { // Link to request GitHub access } <a className="login-link" href={`https://github.com/login/oauth/authorize?scope=user&client_id=${client_id}&redirect_uri=${redirect_uri}`} onClick={() => { setData({ ...data, errorMessage: "" }); }} > <GithubIcon /> <span>Login with GitHub</span> </a> </> )} </div> </div> </section> </Wrapper> ); } const Wrapper = Styled.section` .container { display: flex; justify-content: center; align-items: center; height: 100vh; font-family: Arial; > div:nth-child(1) { display: flex; flex-direction: column; justify-content: center; align-items: center; box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2); transition: 0.3s; width: 25%; height: 45%; > h1 { font-size: 2rem; margin-bottom: 20px; } > span:nth-child(2) { font-size: 1.1rem; color: #808080; margin-bottom: 70px; } > span:nth-child(3) { margin: 10px 0 20px; color: red; } .login-container { background-color: #000; width: 70%; border-radius: 3px; color: #fff; display: flex; align-items: center; justify-content: center; > .login-link { text-decoration: none; color: #fff; text-transform: uppercase; cursor: default; display: flex; align-items: center; height: 40px; > span:nth-child(2) { margin-left: 5px; } } .loader-container { display: flex; justify-content: center; align-items: center; height: 40px; } .loader { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 12px; height: 12px; animation: spin 2s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } } } } `;
在Login.js組件內部,請注意如下重要事項:服務器
讓咱們更新Home.js組件以顯示一些用戶數據,例如(頭像,姓名,關注者人數等)
import React, { useContext } from "react"; import { Redirect } from "react-router-dom"; import Styled from "styled-components"; import { AuthContext } from "../App"; export default function Home() { const { state, dispatch } = useContext(AuthContext); if (!state.isLoggedIn) { return <Redirect to="/login" />; } const { avatar_url, name, public_repos, followers, following } = state.user const handleLogout = () => { dispatch({ type: "LOGOUT" }); } return ( <Wrapper> <div className="container"> <button onClick={()=> handleLogout()}>Logout</button> <div> <div className="content"> <img src={avatar_url} alt="Avatar"/> <span>{name}</span> <span>{public_repos} Repos</span> <span>{followers} Followers</span> <span>{following} Following</span> </div> </div> </div> </Wrapper> ); } const Wrapper = Styled.section` .container{ display: flex; flex-direction: column; height: 100vh; font-family: Arial; button{ all: unset; width: 100px; height: 35px; margin: 10px 10px 0 0; align-self: flex-end; background-color: #0041C2; color: #fff; text-align: center; border-radius: 3px; border: 1px solid #0041C2; &:hover{ background-color: #fff; color: #0041C2; } } >div{ height: 100%; width: 100%; display: flex; font-size: 18px; justify-content: center; align-items: center; .content{ display: flex; flex-direction: column; padding: 20px 100px; box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2); width: auto; img{ height: 150px; width: 150px; border-radius: 50%; } >span:nth-child(2){ margin-top: 20px; font-weight: bold; } >span:not(:nth-child(2)){ margin-top: 8px; font-size: 14px; } } } } `;
最後一步是建立代理服務器,以幫助咱們繞過CORS錯誤。它將是一個簡單的express應用程序,咱們將在header中啓用「Access-Control-Allow-Origin」。咱們將使用它來轉發請求和接收來自Github API的響應,並將所需的響應發送回客戶端(咱們的React應用程序)。將這些變量添加到.env文件中:
REACT_APP_PROXY_URL=http://localhost:5000/authenticate SERVER_PORT=5000
在根文件夾中,建立一個名爲server的文件夾,並在其中建立一個index.js文件。
const express = require("express"); const bodyParser = require("body-parser"); const FormData = require("form-data"); const fetch = require("node-fetch"); const { client_id, redirect_uri, client_secret } = require("./config"); const config = require("./config"); const app = express(); app.use(bodyParser.json()); app.use(bodyParser.json({ type: "text/*" })); app.use(bodyParser.urlencoded({ extended: false })); // Enabled Access-Control-Allow-Origin", "*" in the header so as to by-pass the CORS error. app.use((req, res, next) => { res.header("Access-Control-Allow-Origin", "*"); next(); }); app.post("/authenticate", (req, res) => { const { code } = req.body; const data = new FormData(); data.append("client_id", client_id); data.append("client_secret", client_secret); data.append("code", code); data.append("redirect_uri", redirect_uri); // Request to exchange code for an access token fetch(`https://github.com/login/oauth/access_token`, { method: "POST", body: data, }) .then((response) => response.text()) .then((paramsString) => { let params = new URLSearchParams(paramsString); const access_token = params.get("access_token"); // Request to return data of a user that has been authenticated return fetch(`https://api.github.com/user`, { headers: { Authorization: `token ${access_token}`, }, }); }) .then((response) => response.json()) .then((response) => { return res.status(200).json(response); }) .catch((error) => { return res.status(400).json(error); }); }); const PORT = process.env.SERVER_PORT || 5000; app.listen(PORT, () => console.log(`Listening on ${PORT}`));
附加連接:
https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
https://www.graphql.college/implementing-github-oauth-flow/
若是你按照上面列出的幾個步驟進行操做,則能夠在應用程序中無縫集成「使用Github登陸」功能。
在此處獲取源代碼:Github