React + Authentication で OpenIDConnect 認証を行う
こんにちは。安政です。
弊社では、ユーザ認証に Firebase の Authentication のパスワード認証を用いています。
OpenIDConnect を用いて、Auth0 を用いた SSO を実装する機会がありましたので、簡単にですが、共有したいと思います。
Firebase Authentication の設定
Authentication にて OpenIDConnect を有効にするための方法を記します。
-
Authentication の 「Sign-in Method」を開く
-
「新しいプロバイダを追加」をクリック
-
カスタムプロバイダ上の「OpenID Connect」を選択
a. 初回は Authentication のアップグレードを行う必要がある -
OIDC プロバイダ情報を定義する
a. 画像のような形で定義を行います。クライアント ID, 発行元, クライアントシークレットは認可サーバから定義されたものを適宜指定してください。b. プロバイダ ID は任意の値を設定できます。React 側で利用する必要があるので、メモしておきましょう。
c. コールバック URL が発行されるので、認可サーバの方に設定します。
※ Auth0 を認可サーバとして用いる場合、設定した Application 内の以下の項目に設定を行う必要があります。
- Allowed Callback URLs: 4-c で発行されたコールバック URL を記載します
- Allowed Web Origins: 認証を行うシステムの URL を記載します
React で OIDC を用いたログイン処理を行う
今回は React のカスタムフックの形で、ログイン処理を実装しました。
サンプルコードとしてはこちらです。
import {
getAuth,
OAuthProvider,
signInWithPopup,
signOut as signOutFirebase,
AuthError
} from 'firebase/auth'
export default function useOidcSignin(): [Result, () => Promise<Viewer>] {
// 弊社のログイン処理
const client = useApolloClient()
const { setViewer } = useViewerContext()
// Authentication の設定で指定したプロバイダID を指定
const oidcProviderId = 'oidc.test-provider-id'
// OIDC を利用したプロバイダーの定義
const provider = new OAuthProvider(oidcProviderId)
const auth = getAuth()
const [
{ loading, error, value: viewer },
auth0Siginin
] = useAsyncFn(async (): Promise<Viewer> => {
try {
// このタイミングで、OpenIDConnect 認可先(Auth0)の認証ページが開く
const result = await signInWithPopup(auth, provider)
// Firebase での認証情報を取得
const accessToken = await result.user.getIdToken()
const userInfo = jwt_decode(accessToken)
// 外部からのデータ取得なので、TypeGuard を用いて意図したデータが取得されているかの判定
if (!dataIsUserInfoData(userInfo)) {
setErrorMessage('ユーザ情報の取得に失敗しました')
throw new TypeError('ユーザ情報の取得に失敗しました')
}
// 認証後に何らかの処理を行う場合、ここに記載する
} catch (e) {
// ログイン処理等で失敗した場合、Firebase からログアウトする
await signOutFirebase(auth)
// 認可先(auth0)でエラーが発生した想定
const error = e as AuthError
if (error.code == 'auth/invalid-credential') {
const responseRe = new RegExp(
'response: (.*)(auth/invalid-credential).',
'g'
)
const response = responseRe.exec(error.message)
if (response) {
window.location.href =
process.env.ERROR_PAGE + '?' + response[1]
}
}
throw new Error('問題が発生しました。しばらく時間を置いて再度お試しください。')
}
// ログインして viewer の状態が変化したので更新
// KANNA の通常のログイン処理
const viewer = await loadViewer(client)
setViewer && setViewer(viewer)
return viewer
}, [])
return [{ loading, error, viewer }, auth0Siginin]
}
// auth0 より受信したデータをパースしたデータを保持する
interface UserInfoResponse {
email: string
firebase: {
// 認可先(Auth0)で指定されたデータを取得できる
sign_in_attributes: {
app_metadata: {
organization_data: {
id: string
metadata: {
company_name: string
}
}
}
user_metadata: {
first_name: string
last_name: strin
}
}
}
}
// 必要なデータが取得できているかの TypeGuard
const dataIsUserInfoData = (data: unknown): data is UserInfoResponse => {
const userInfo = data as UserInfoResponse
if (userInfo.email === undefined) return false
if (userInfo.firebase === undefined) return false
if (userInfo.firebase.sign_in_attributes === undefined) return false
// 以下は Auth0 向けの処理
if (userInfo.firebase.sign_in_attributes.user_metadata === undefined)
return false
if (userInfo.firebase.sign_in_attributes.app_metadata === undefined)
return false
if (
userInfo.firebase.sign_in_attributes.app_metadata.organization_data ===
undefined
)
return false
return true
}
大事な点としては、
// Authentication の設定で指定した プロバイダID を指定
const oidcProviderId = 'oidc.test-provider-id'
// OIDC を利用したプロバイダーの定義
const provider = new OAuthProvider(oidcProviderId)
const auth = getAuth()
// このタイミングで、OpenIDConnect 認可先(Auth0)の認証ページが開く
const result = await signInWithPopup(auth, provider)
この辺りとなります。
Firebase による認証を素直に利用している場合は、getAuth
の利用のみで済むかと思います。
弊社では、auth の変化を onAuthStateChanged
を用いて監視することで、ログイン状態の管理を行なっています。
最後に
とても簡単にですが、サンプルコードをベースに参考になりましたら、幸いです。
アプリ(React-Native)での Authentication を用いた OIDC の処理はもう少し複雑であったり、ライブラリにバグがあったり(2022/11 時点)で苦戦しました。
また機会がありましたら、コード中心になりますが、共有できればと思います。
株式会社アルダグラムのTech Blogです。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら herp.careers/v1/aldagram0508/
Discussion