使用Okta实现React的登录功能
成果

环境
-
- 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中找不到。

解决方法可以有很多种。
在签入后,需要调用一个名为 getUserInfo() 的函数。
当使用Okta API时遭遇跨域资源共享错误问题。
现象

解决方案
在Okta的设置页面中,找到”Security > API > Trusted Origins”,进行CORS(跨源资源共享)的授权设置。
有一个问题出现了警告(移动的东西)。
现象
检测到两个自定义的 restoreOriginalUri 回调函数。来自 OktaAuth 配置的那一个将被提供的 restoreOriginalUri 属性来自 Security 组件的覆盖。

处理办法
原因是在React的StrictMode下,组件被渲染了两次。根据上面的链接,可以在useEffect的清理函数中进行相应的处理。
Okta的设置
基本设定 (jī shè

-
- [Sign-in method]
OIDC – OpenID Connect
[Application type]
Single-Page Application

-
- [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

-
- Base URIs
空でOK
Assignments
Skip group assignment for now

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

跨域资源共享的许可
我們將設定可以訪問Okta API的域名。
这是我在「入坑要点」中介绍的导致“在签出时发生下述错误”问题的原因。
如果不这样做,就无法有效地调用Okta的API,应用程序(React部分)会报错。


-
- [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
实施
按照以下步骤进行。 .)
-
- 创建config/auth.js文件
-
- 创建hooks/use-auth.js文件
-
- 编辑index.js文件
-
- 编辑App.js文件
- 编辑App.css文件
创建config/auth.js
客户身份证件

帐号的域名 de

// 例)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中获取,无需调用以上函数。

编辑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;
}
就是这样。


最后
解决「idToken中用户信息不完整的问题」有些困难。
在休息日里动手做事时,不知不觉地想要玩乐,没有思考地忙碌起来,结果浪费了相当多的时间。
我希望在休息日或工作时间,都能拥有不偷懒地阅读官方文件的坚强心态。