В това ръководство ще създадем примерно B2B приложение в React, където потребителите могат да се регистрират, да влизат, да управляват акаунтите си и да преглеждат информация за организацията и членовете, като всичко това използва PropelAuth, React и FastAPI.

Ще използваме следните технологии за тази публикация в блога:

  • React.js — за нашия интерфейс
  • FastAPI (python) — за нашия бекенд
  • PropelAuth — за потребителско влизане и управление

Пълният код на ръководството ще бъде достъпен в тези репо Github.

Настройване на страници за удостоверяване

Преди да създадем нашето приложение, първо ще настроим нашия проект в PropelAuth. Проектите в PropelAuth предоставят всички необходими компоненти за удостоверяване за вашите приложения, включително хоствани страници за вход, които ще използваме за това ръководство. Можете също да настроите допълнителни функции като RBAC, SAML, социални влизания/SSO и други. За повече информация как да добавите тези опции, не забравяйте да разгледате нашата документация.

Първата стъпка е да създадете проект в PropelAuth.

След като проектът бъде създаден, ще бъдете насочени към таблото за управление за вашия проект, очертавайки стъпките за завършване на добавянето на удостоверяване във вашето приложение.

Първата стъпка е да персонализирате външния вид на вашите хоствани страници за удостоверяване. Като щракнете върху „Преглед“, ще бъдете пренасочени към тази страница:

Тук можете да коригирате външния вид на всички хоствани страници за удостоверяване, за да съответства най-добре на предпочитаната от вас марка и указания за стил. Чувствайте се свободни да правите промени, щракнете върху „Запазване“ и се върнете обратно към таблото за управление, като използвате лявата странична лента. Сега, след като променихме външния вид на нашите страници, щракнете върху „Маркиране като готово“ и преминете към следващата стъпка.

Оттук можете да конфигурирате други аспекти на вашия опит с удостоверяване на крайните потребители, включително:

  • Добавяне на „Вход с Google“ или други доставчици на SSO
  • Събиране на допълнителни метаданни при регистрация — като потребителско име или собствено/фамилно име
  • Разрешаване на вашите потребители да качват своя собствена профилна снимка
  • Позволяване на вашите потребители да създават организации и да канят своите колеги (наречено B2B поддръжка)

Засега ще щракнете върху „Маркиране като готово“ на стъпка 2, „Добавяне на социални входове“ и ще преминем към стъпка 3.

Регистрирайте се като тестов потребител

Под стъпка 3 щракнете върху „Преглед“ и трябва да се отвори нов раздел със страницата за удостоверяване, която сте конфигурирали по-горе. Регистрирайте се, сякаш сте потребител, излезте от раздела и маркирайте стъпката като извършена.

Създайте проект React

Ако все още нямате проект, можете да създадете такъв с:

$ npx create-react-app frontend

След това инсталирайте пакета @propelauth/react. Той предоставя лесен интерфейс за достъп до информацията за вашите потребители. Той ще управлява токените за удостоверяване вместо вас и има хубави функции като опресняване на информация за удостоверяване, когато потребителят се свърже отново с интернет или превключи обратно към вашия раздел.

$ npm install @propelauth/react

И накрая, ще инсталираме react-router, за да използваме превключване между страници в нашето приложение.

$ npm install react-router-dom@6

Frontend

В този раздел ще настроим секциите на интерфейса на нашето приложение React, като ще разгледаме как да интегрираме PropelAuth във вашите компоненти, докато вървим.

Настройте доставчик на удостоверяване

Първо отидете до файла index.js във вашето приложение. Тук ще добавим нашия AuthProvider и BrowserRouter от react-router.

import {AuthProvider} from '@propelauth/react';
import {BrowserRouter} from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <AuthProvider authUrl={process.env.REACT_APP_PROPELAUTH_AUTH_URL}>
	    <BrowserRouter>
	      <App/>
	    </BrowserRouter>
    </AuthProvider>
  </React.StrictMode>
);

AuthUrl е наличен в раздела Интегриране на предния екран на вашия проект PropelAuth или стъпка 4 в таблото ви за управление.

BrowserRouter ще управлява нашата хронология на URL адресите, докато превключвате страници в нашето приложение. AuthProvider е доставчик на React context, който управлява access token и метаданни на текущия потребител, и всички други компоненти извличат информация от то. Поставяме го на най-високото ниво на нашето приложение, така че никога да не се демонтира.

Вземете статус за влизане

След това в App.js ще въведем функцията withAuthInfo, която инжектира информация за потребителя в компонент на React. В нашия проект ще показваме различни компоненти в зависимост от това дали потребителят е влязъл или не.

import { withAuthInfo } from '@propelauth/react';

const App = withAuthInfo(({isLoggedIn}) => {
  if (isLoggedIn) {
      return <div>The User is logged in</div>
  } else {
      return <div>The User is logged out</div>
  }
})

export default App;

Използвайки кода по-горе, ако потребителят е влязъл, той трябва да види следното:

Създаване на бутони за вход/изход

@propelauth/react също така предоставя кукички на React за пренасочване на вашите потребители към хостваните страници за вход/регистрация/акаунт, които сте създали във вашия проект PropelAuth, или излизане на вашите потребители. Нека ги добавим към нашия файл App.js.

import { useLogoutFunction, useRedirectFunctions, withAuthInfo } from '@propelauth/react';

const App = withAuthInfo(({isLoggedIn}) => {
  const logoutFn = useLogoutFunction()
  const {redirectToSignupPage, redirectToLoginPage} = useRedirectFunctions();
    
  if (isLoggedIn) {
      return <div>
          The User is logged in
          <button onClick={() => logoutFn()}>
              Click here to log out
          </button>
      </div>
  } else {
      return <div>
          To get started, please log in as test user.
          <br/>
          <button onClick={() => redirectToSignupPage()}>
              Sign up
          </button>
          <button onClick={() => redirectToLoginPage()}>
              Log in
          </button>
      </div>
  }
})

Сега, ако потребител е излязъл, той ще види следното:

И ако са влезли, ще видят:

Показване на потребителска информация

Следващата стъпка е да изградим нашите маршрути и да създадем маршрут и компонент, който ще показва информацията на текущо влезлия потребител. Първо създайте components/Home.jsx и components/UserInfo.jsx, които ще служат съответно като компонент на нашата начална страница и маршрут за потребителска информация.

В Home.jsx ще импортираме withRequiredAuthInfo, което е идентично на withAuthInfo, но компонентът няма да бъде изобразен, ако потребителят е не е влязъл и вместо това по подразбиране ще пренасочи към хостваната страница за регистрация, освен ако не е посочено друго. Също така ще импортираме Връзка от react-router-dom. Накрая изградете останалата част от компонента с помощта на Връзка:

import {withRequiredAuthInfo} from "@propelauth/react";
import {Link} from "react-router-dom";

function Home(props) {
   return <div>
        <Link to="/user_info">
            Click Here to see user info
        </Link>
   </div>
}

export default withRequiredAuthInfo(Home);

В UserInfo.jsx ще създадем компонент за показване на потребителска информация, който изтегля обекта user, който се инжектира автоматично от withAuthInfo.

import {withAuthInfo} from '@propelauth/react';

function UserInfo({user}) {
    return <span>
        <h2>User Info</h2>
        {user && user.pictureUrl && <img src={user.pictureUrl} alt={"profile"} className="pictureUrl" />}
        <pre>user: {JSON.stringify(user, null, 2)}</pre>
    </span>
}

export default withRequiredAuthInfo(UserInfo);

И накрая, в App.js ще настроим нашите маршрути за компонентите Home и User Info в проверката isLoggedIn:

if (isLoggedIn) {
      return <div>
          The User is logged in
          <button onClick={() => logoutFn()}>
              Click here to log out
          </button>
          <Routes>
            <Route exact path="/" element={<Home/>}/>
            <Route path="/user_info" element={<UserInfo/>}/>
          </Routes>
      </div>
  }

Потребителите вече трябва да могат да кликнат върху връзката, ако са влезли в компонента Home и да видят информацията си.

Изпращане на заявки от Frontend към Backend

До този момент ние останахме на предните компоненти на нашето приложение, но след това ще правим заявки към защитен бекенд.

Правене на удостоверени заявки

За да направите удостоверена заявка от името на вашия потребител, ще трябва да предоставите токен за достъп. Точно като isLoggedIn и user, маркерът за достъп е достъпен от withAuthInfo. Предоставяте го в заявката в заглавката за оторизация, така:

Authorization: Bearer ACCESS_TOKEN

С fetch това изглежда така:

function fetchWhoAmI(accessToken) {
    return fetch("/whoami", {
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${accessToken}`
        }
    }).then(response => {
        if (response.ok) {
            return response.json()
        } else {
            return {status: response.status}
        }
    })
}

Можем да добавим това към нов компонент components/AuthenticatedRequest.jsx с помощта на куката useEffect на React.

import {withRequiredAuthInfo} from "@propelauth/react";
import {useEffect, useState} from "react";

function fetchWhoAmI(accessToken) {
    return fetch("/whoami", {
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${accessToken}`
        }
    }).then(response => {
        if (response.ok) {
            return response.json()
        } else {
            return {status: response.status}
        }
    })
}

function AuthenticatedRequest({accessToken}) {
    const [response, setResponse] = useState(null);
    useEffect(() => {
        fetchWhoAmI(accessToken).then(setResponse)
    }, [accessToken])

    return <span>
        <h2>Server Response</h2>
        <pre>{response ? JSON.stringify(response, null, 2) : "Loading..."}</pre>
    </span>
}

export default withRequiredAuthInfo(AuthenticatedRequest);

Кратка бележка за CORS

Нашето React приложение работи на порт 3000, така че ще трябва да стартираме нашия бекенд на различен порт (в този урок използваме 3001). От съображения за сигурност браузърите няма да ви позволят да правите заявки от един домейн към друг, а http://localhost:3000 и http://localhost:3001 се считат за различни домейни.

Лесен начин да коригирате този проблем е да добавите следното към вашия package.json:

"proxy": "<http://127.0.0.1:3001/>"

Това автоматично ще прокси определени заявки (като JSON заявки) до http://127.0.0.1:3001. За повече информация вижте официалните документи на React.

Страхотно, вече можем да правим удостоверени заявки към всеки бекенд, който пожелаем. Единственият проблем? Все още нямаме бекенд — нека поправим това.

Удостоверяване в FastAPI

Създаване на виртуална среда

Първо създаваме нова виртуална среда и инсталираме нашите зависимости. Ще използваме propelauth-fastapi, за да потвърдим токена за достъп, изпратен от интерфейса. Нуждаем се също от uvicorn, за да стартираме нашето приложение.

$ mkdir backend
$ cd backend
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install fastapi "uvicorn[standard]" propelauth-fastapi python-dotenv

Създаване на нашия защитен маршрут

Кодът е достатъчно прост, за да можем първо да го разгледаме и да го обясним след това:

#main.py
import os

from dotenv import load_dotenv
from fastapi import Depends, FastAPI
from propelauth_fastapi import init_auth
from propelauth_py.user import User

load_dotenv()

app = FastAPI()
auth = init_auth(os.getenv("PROPELAUTH_AUTH_URL"), os.getenv("PROPELAUTH_API_KEY"))

@app.get("/whoami")
def who_am_i(user: User = Depends(auth.require_user)):
    return {"user_id": user.user_id}
  • auth.require_user е зависимост от FastAPI, която валидира токена за достъп. Ако не е предоставен валиден токен за достъп, заявката се отхвърля с грешка 401 Unauthorized. Ако искате заявката да продължи дори без валиден токен за достъп, използвайте **auth.optional_user вместо това.**‍
  • PROPELAUTH_AUTH_URL и PROPELAUTH_API_KEY могат да бъдат намерени, като щракнете върху Backend Integration в страничната лента на вашия проект PropelAuth. Те се използват веднъж при стартиране за извличане на информацията, необходима за валидиране на токени. След това токените за достъп (които са JWT) се валидират бързо, без да е необходимо да се правят външни заявки.

Можете да стартирате кода с:

$ uvicorn main:app --reload --port=3001

Вашите заявки от браузъра трябва да бъдат успешни, когато сте влезли и 401, когато сте излезли.

Сега, когато имаме работещ бекенд, ако сега включим нашите components/AuthenticatedRequest.jsx в нов маршрут на App.js:

<Route path="/auth" element={<AuthenticatedRequest/>}/>

И ако добавим нова връзка към components/Home.jsx:

<Link to="/auth">
    Click Here to see an authenticated request to the backend
</Link>

Ако потребителят е влязъл, той трябва да види отговора на сървъра от нашата удостоверена заявка:

Информация за организацията

Често срещан случай на използване на B2B приложения е възможността потребителите да се разделят на организации или екипи. За целите на тази компилация на приложението ще добавим функционалност за преглед на списъка с организации, част от които е настоящият потребител, и за преглед на други членове на тази организация.

Показване на организации

Първо ще създадем components/ListOfOrgs.jsx, който ще изброи всички организации, от които е част потребител, или ще покаже бутон за пренасочване към създадената организация/ страница с покани, ако не са част от никакви организации.

import {useRedirectFunctions, withRequiredAuthInfo} from "@propelauth/react";
import {Link} from "react-router-dom";

function NoOrganizations() {
    const {redirectToCreateOrgPage} = useRedirectFunctions()

    return <div>
        You aren't a member of any organizations.<br/>
        You can either create one below, or ask for an invitation.<br/>
        <button onClick={redirectToCreateOrgPage}>
            Create an organization
        </button>
    </div>
}

function ListOrganizations({orgs}) {
    return <>
        <h3>Your organizations</h3>
        <ul>
            {orgs.map(org => {
                return <li key={org.orgId}>
                    <Link to={`/org/${org.urlSafeOrgName}`}>
                        {org.orgName}
                    </Link>
                </li>
            })}
        </ul>
    </>
}

function ListOfOrgs(props) {
    const orgs = props.orgHelper.getOrgs()
    if (orgs.length === 0) {
        return <NoOrganizations />
    } else {
        return <ListOrganizations orgs={orgs}/>
    }
}

// By default, if the user is not logged in they are redirected to the login page
export default withRequiredAuthInfo(ListOfOrgs);

orgHelper ни позволява лесно да получим всички организации, на които потребителят е член, и като предаваме този масив в нашия помощен компонент ListOrganizations, ние може да картографира всяка организация и да създаде нова Връзка и динамичен маршрут за показване на подходяща информация.

След това ще създадем components/OrgInfo.jsx и, следвайки същия модел, който направихме с AuthenticatedRequest.jsx, ще използваме useEffect на React strong>» кука за изпращане на извличане към нов маршрут, org/orgId.

import {withAuthInfo} from '@propelauth/react';
import {useParams} from "react-router-dom";
import { useEffect, useState } from "react";

function fetchOrgInfo(orgId, accessToken) {
    return fetch(`/org/${orgId}`, {
        headers: {
            "Content-Type": "application/json",
            "Authorization": `Bearer ${accessToken}`
        }
    }).then(response => {
        if (response.ok) {
            return response.json()
        } else {
            return {status: response.status}
        }
    })
}

function OrgInfo({ orgHelper, accessToken }) {
    const {orgName} = useParams();
    const orgId = orgHelper.getOrgByName(orgName).orgId

    const [response, setResponse] = useState(null)
    
    useEffect(() => {
        fetchOrgInfo(orgId, accessToken).then(setResponse)
    }, [orgId, accessToken])

    return <div>
        <p>{response ? JSON.stringify(response) : "Loading..."}</p>
    </div>
}

export default withRequiredAuthInfo(OrgInfo);

След това трябва да създадем маршрута FastAPI, за да върнем удостоверен отговор, така че в нашия файл main.py ще добавим нов маршрут:

@app.get("/org/{org_id}")
async def view_org(org_id: str, current_user: User = Depends(auth.require_user)):
    org = auth.require_org_member(current_user, org_id)
    return {"org": org}

Накрая трябва да покажем новите маршрути в нашето приложение. В App.js трябва да импортираме и създадем нови пътища за Route за ListOfOrgs.jsx и OrgInfo.jsx

<Route path="/orgs" element={<ListOfOrgs/>}/>
<Route path="/org/:orgName" element={<OrgInfo/>}/>

И в Home.jsx трябва да добавим нова Връзка към пътя /orgs, за да покажем ListOfOrgs.jsx

<Link to="/orgs">
    Click Here to see org info
</Link>

Ще оставим на вас да го направите да изглежда добре :)

Завършване

Това ръководство предоставя цялостен поглед върху рамката на приложението за удостоверяване, която можете да използвате, за да започнете. Използвайки PropelAuth за удостоверяване, успяхме да пропуснем изграждането на потребителски интерфейси за удостоверяване, транзакционни имейли, потоци от покани и други.

Нашият интерфейс отправи удостоверена заявка към нашия бекенд и нашият бекенд успя да идентифицира потребителя, който е направил заявката. Можете да използвате това за неща като запазване на информация в база данни за user_id.

Ако имате въпроси, моля, свържете се с [email protected].