使用Okta实现React的登录功能

成果

a_3.png

环境

    • React

^18.2.0

react-router-dom

^6.9.0

@okta/okta-react

^6.7.0

@okta/okta-auth-js

^7.2.0

※只摘录主要内容

想要吸引的读者

    • Oktaのみでログイン機能を実装してみたい人

 

    次に紹介する「ハマったポイント」にハマっている人

如果你想要在Okta+Cognito上做类似于React的事情,请参考以下链接。

 

沉迷于的要点

包含在idToken中的用户信息很少的问题

事件

我原本期望Okta用户的个人资料包含了所有信息,但事实证明并非如此。

我想获取下面这个Okta用户的名字和姓氏,但是在idToken中找不到。

a_4.png

解决方法可以有很多种。

在签入后,需要调用一个名为 getUserInfo() 的函数。

当使用Okta API时遭遇跨域资源共享错误问题。

现象

a_6.png

解决方案

在Okta的设置页面中,找到”Security > API > Trusted Origins”,进行CORS(跨源资源共享)的授权设置。

有一个问题出现了警告(移动的东西)。

现象

检测到两个自定义的 restoreOriginalUri 回调函数。来自 OktaAuth 配置的那一个将被提供的 restoreOriginalUri 属性来自 Security 组件的覆盖。

スクリーンショット 2023-03-25 14.21.59.png

处理办法

 

原因是在React的StrictMode下,组件被渲染了两次。根据上面的链接,可以在useEffect的清理函数中进行相应的处理。

Okta的设置

基本设定 (jī shè

o_1.png
    • [Sign-in method]

OIDC – OpenID Connect

[Application type]

Single-Page Application

o_2.png
    • [App integration name]

My React App(※任意でOK)

[Grant type]

Authorization Code

[Sign-in redirect URIs]

http://localhost:3000/login/callback

[Sign-out redirect URIs]

http://localhost:3000

o_3.png
    • Base URIs

空でOK

Assignments

Skip group assignment for now

o_4.png

指派用户

分配具有使用上述创建的信号机制的Okta用户。

o_7.png

跨域资源共享的许可

我們將設定可以訪問Okta API的域名。

这是我在「入坑要点」中介绍的导致“在签出时发生下述错误”问题的原因。

如果不这样做,就无法有效地调用Okta的API,应用程序(React部分)会报错。

スクリーンショット 2023-03-26 8.54.37.png
o_10.png
    • [Origin name]

localhost:3000

[Origin URL]

http://localhost:3000

[Choose Type]

下記2つをチェック

Cross-Origin Resource Sharing(CORS)
Redirect

Okta的配置已经完成。

创建React应用

使用create-react-app进行创建。

如果您使用VITE或其他工具来创建的话,由于端口的变化,所以要注意之前在“添加原点”中设置的回调URL需要进行修改。

在您自己的工作目录中创建一个 React 项目。

npx create-react-app react-okta-20230326

附加库

我们将在上述创建的React项目中添加所需的库。

yarn add @okta/okta-react @okta/okta-auth-js react-router-dom

目录结构。

这是一个已完善的目录结构。

.
├── node_modules/
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── config
│   │   └── auth.js
│   ├── hooks
│   │   └── use-auth.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── reportWebVitals.js
│   └── setupTests.js
└── yarn.lock

实施

按照以下步骤进行。 .)

    1. 创建config/auth.js文件

 

    1. 创建hooks/use-auth.js文件

 

    1. 编辑index.js文件

 

    1. 编辑App.js文件

 

    编辑App.css文件

创建config/auth.js

客户身份证件

o_12.png

帐号的域名 de

o_13.png
// 例)0123456789abcdefghij
const CLIENT_ID = 'Client ID';
// 例)https://dev-12345678.okta.com/oauth2/default
const ISSUER = 'https://アカウントのドメイン/oauth2/default';
const REDIRECT_URI = `${window.location.origin}/login/callback`;

// eslint-disable-next-line
export default {
  oidc: {
    clientId: CLIENT_ID,
    issuer: ISSUER,
    redirectUri: REDIRECT_URI,
    scopes: [
      "openid",
      "email",
      "profile",
    ],
    pkce: true,
  }
};

请创建hooks/use-auth.js文件。

import React, { createContext, useContext, useEffect, useState } from 'react';
import { useOktaAuth } from '@okta/okta-react';
import { OktaAuth, toRelativeUrl } from '@okta/okta-auth-js';
import config  from '../config/auth';

export const oktaAuth = new OktaAuth(config.oidc);

const authContext = createContext({});

export const ProvideAuth = ({children}) => {

  const auth = useProvideAuth();
  return (
    <authContext.Provider value={auth}>
      {children}
    </authContext.Provider>
    )
  ;
}

export const useAuth = () => {
  return useContext(authContext);
}

const useProvideAuth = () => {

  const { authState } = useOktaAuth();

  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [username, setUserName] = useState('ゲスト');
  const [user, setUser] = useState(null);

  useEffect(() => {
    if(!authState || !authState.isAuthenticated) return;
    console.log(authState);
    oktaAuth.token.getUserInfo()
      .then(e => {
        setUser(e);
        setUserName(e.name);
        setIsAuthenticated(true);
      });
  }, [authState]);

  const signIn = () => {
    const originalUri = toRelativeUrl(window.location.href, window.location.origin);
    oktaAuth.setOriginalUri(originalUri);
    oktaAuth.signInWithRedirect();
  };

  const signOut = async () => {
    oktaAuth.signOut();
    setUser(null);
    setIsAuthenticated(false);
  };

  return {
    isAuthenticated,
    user,
    username,
    signIn,
    signOut
  }
}

重点在于使用`useProvideAuth`的`useEffect`,通过`oktaAuth.token.getUserInfo()`获取用户信息。

这将成为解决在「卡住点」中提到的「idToken中所包含的用户信息很少的问题」的方法。

尽管如此,由于idToken中包含了用户名称、电子邮件等基本信息,所以如果这些就足够的话,可以直接从idToken中获取,无需调用以上函数。

スクリーンショット 2023-03-26 9.55.34.png

编辑index.js文件

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

编辑App.js

import './App.css';
import { useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { toRelativeUrl } from '@okta/okta-auth-js';
import { Security } from '@okta/okta-react';
import { Routes, Route } from 'react-router-dom';
import { LoginCallback } from '@okta/okta-react';

import { useAuth, oktaAuth, ProvideAuth } from './hooks/use-auth';

const Profile = () => {

  const { isAuthenticated, user, username, signIn, signOut } = useAuth();

  return (
    <>
      <main className='App'>
        <p>ようこそ{ username }さん</p>
        {
          isAuthenticated ? (
            <>
              <ul>
                { 
                  Object.keys(user)
                    .filter(key => key !== 'headers')
                    .map(key => (
                      <li key={key}> {key} : {user[key]} </li>
                    ))
                }
              </ul>
              <button onClick={() => signOut()}>ログアアウト</button>
            </>
          ) : (
            <button onClick={() => signIn()}>ログイン</button>
          )
        }
        
      </main>
    </>
  );
}

const App = () => {

  const navigate = useNavigate();
  const restoreOriginalUri = useCallback((_oktaAuth,  originalUri) => {
    navigate(toRelativeUrl(originalUri || '/', window.location.origin));
  }, [navigate])

  useEffect(() => {
    return () => {
      oktaAuth.options.restoreOriginalUri = undefined
    }
  }, [])

  return (
    <>
      <Security oktaAuth={oktaAuth} restoreOriginalUri={restoreOriginalUri}>
        <ProvideAuth>
          <Routes>
            <Route path="/" exact={true} element={<Profile />}/>
            <Route path="login/callback" element={<LoginCallback />}/>
          </Routes>
        </ProvideAuth>
      </Security>
    </>
  );

};
export default App;

重点是通过 useEffect 设置清理函数的地方。

这将成为解决「某个吸引我的问题(动态的)」中所提到的“警告出现”的解决方法。

修改 App.css

.App {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

button {
  font-size: calc(10px + 2vmin);
}

p {
  margin-bottom: 0;
}

ul {
  list-style: none;
  font-size: 16px;
  border: 1px solid #fff;
  padding: 1em;
  margin: 1em 0;
}

就是这样。

a_1.png
a_3.png

最后

解决「idToken中用户信息不完整的问题」有些困难。

在休息日里动手做事时,不知不觉地想要玩乐,没有思考地忙碌起来,结果浪费了相当多的时间。

我希望在休息日或工作时间,都能拥有不偷懒地阅读官方文件的坚强心态。

bannerAds