22
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NRI OpenStandiaAdvent Calendar 2021

Day 23

React hooksとKeycloakを連携してみた

Last updated at Posted at 2021-12-22

はじめに

ReactKeycloakを連携し、OIDCによるログインを実装しましたので、実装方法についてご紹介しようと思います。

reactkeycloak.png

Open ID Connect(OIDC)

OIDCは、OAuthを拡張した認証のためのプロトコルで、シングルサインオン(SSO)に主に使われます。
OIDCの用語は以下になります。

  • OpenID Provider(OP)
    • ユーザの認証を行うサーバー。
  • Relying Party(RP)
    • OPにIDトークンとアイデンティ情報を要求するサーバー。
  • UserInfoエンドポイント
    • アクセストークンを示したクライアントに対し、アイデンティ情報を提供する。
  • IDトークン
    • 認証・認可の情報を含むJWT(JSON Web Token)形式のトークン。
  • アクセストークン
    • UserInfoエンドポイントにアクセスするためのトークン。

今回作成したアプリケーションのフローを以下の図に示します。
flow.png

Keycloakの設定

今回、登録したKeycloakの設定を以下に示します。

  • レルム:Keycloakでユーザなどの情報を管理するための単位。
    • realm:demo
  • クライアント:OIDCにおけるRPを指定します。
    • Client ID:demo-react-client
    • RootURL:http://localhost:3000/
  • ユーザー
    • FirstName:Foo
    • LastName:Smith
    • Email:user001@dummy.com
    • Password:password

JavaScriptアダプター(keycloak.json)の設定

keycloak.jsonで、JavaScriptアダプターの設定を行います。
javaScriptアダプターはKeycloakの機能であるクライアントアダプターの一つで、アプリケーションにOIDCのRPなどの機能を持たせることが可能です。KeycloakのClient設定画面のInstallationタブで「Format Option」にKeycloak OIDC JSONを設定することでJSONファイルをダウンロードすることができます。

{
  "realm": "demo",
  "auth-server-url": "http://localhost:8080/auth/",
  "ssl-required": "external",
  "resource": "demo-react-client",
  "public-client": true,
  "confidential-port": 0
}

各設定内容については以下に示します。

  • realm:レルム名
  • auth-server-url:KeycloakのURL
  • ssl-required:SSLの設定
    • external:プライベートIPアドレスに固定する限り、ユーザーはSSL無しでKeycloakと通信可能。
    • none:SSLの設定なし。
    • all:すべてのIPアドレスに対し、SSLを要求。
  • resource:クライアントの「Client ID」
  • public-client:パブリッククライアントである場合はtrueを設定

Reactでの実装

React 16.8から追加された機能であるReact hooksを使い実装しました。

プロジェクト作成

Create React Appでプロジェクトを作成します。

$ npx create-react-app  [プロジェクト名]

ディレクトリ構成

上記コマンドで作成されたディレクトリに追加したフォルダ・ファイルを示します。
publicディレクトリに前述したkeycloak.jsonを配置します。
また、srcディレクトリにcomponentsディレクトリを作成し、Secured.jsUserinfo.jsLogout.jsを配置します。

.
├── README.md
├── node_modules
├── package.json
├── public
│   ├── keycloak.json (追加)
│   ├── favicon.ico
│   ├── index.html
│   ├── manifest.json
│   └── robots.txt
├── src
│   ├──components(追加)
│   │   ├── Secured.js
│   │   ├── Userinfo.js
│   │   ├── Logout.js
│   ├── App.css
│   ├── App.js
  ・
  ・
  ・
 (省略)

Keycloakとの連携

Secured.js
import {React,useState,useEffect} from 'react'
import Keycloak from 'keycloak-js';
import UserInfo from './UserInfo';
import Logout from './Logout';

const Secured = () => {
    const [keycloakState, setKeycloakState] = useState({ keycloak: null, authenticated: false });

    useEffect(() => {
        const keycloak = Keycloak('./keycloak.json');
        //ページロード時の処理で、Keycloak.init関数を呼び出し、未認証の場合
        //認可コードフローを開始しログインページを表示
        keycloak.init({onLoad: 'login-required'}).then(authenticated => {
            setKeycloakState({ keycloak: keycloak, authenticated: authenticated });      
        })
    }, [])
    console.log(keycloakState)
    if(keycloakState.keycloak){

        if(keycloakState.authenticated)
        return (
            <div>
                <p>login success</p>
                <UserInfo keycloak={keycloakState} />
                <Logout keycloak={keycloakState} />
            </div>
            ) ; 
        else 
        return(<div> 認証できません</div>)
        }
    return (
        <div>Keycloakを初期化しています...</div>
    )
}

export default Secured

http://localhost:3000/Securedにアクセスし、確認します。

login.png

UseInfoの表示

Userinfo.js
import {useEffect,useState} from 'react'

const UserInfo = (props) => {

    const keycloakState = props.keycloak
    const [user, setUser] = useState({name:"",email:"",id:""});
    
    useEffect(() =>{
      //loadUserInfoから取得したユーザーデータを元にuserを更新
      keycloakState.keycloak.loadUserInfo().then(UserInfo => {
            console.log(UserInfo);
            setUser({ name: UserInfo.name, email: UserInfo.email, id: UserInfo.sub })
        });
    },[])
    return (
        <div className="UserInfo">
        <p>Name: {user.name}</p>
        <p>Email: {user.email}</p>
        <p>ID: {user.id}</p>
        </div>
    )
}
export default UserInfo

ここでは、Keycloakインスタンスから、loadUserInfoメソッドを使用しユーザーデータの抽出をしています。
ログインが成功した後の画面に名前、メールアドレス、クライアントIDが表示されます。

userinfo.png

ログアウトの実装

ここも同様に、Keycloakインスタンスからlogoutメソッドを使用します。
react-routerv6からuseHistoryメソッドが使用できなくなったため、useNavigateメソッドを使用しログアウトを実装しました。
react-routerv6v5の違いについては、以下を参考にしてください。

Logout.js
import {React} from 'react'
import {useNavigate} from 'react-router-dom'

const Logout = (props) => {

    const keycloakState = props.keycloak
    // ログイン画面に遷移
    const nabigate = useNavigate()
    nabigate('/secured') 
    return (
        <div>
            <button onClick={ () => keycloakState.keycloak.logout()} >
            Logout 
            </button>
        </div>
    )
}
export default Logout

まとめ

今回は、シンプルなReactアプリケーションとKeycloakを連携し、簡単にフロントエンドの認証を実装できることがわかりました。
Keycloakではシングルサインオンや外部プロバイダーを使った認証なども実装することができるので、色々活用してください。

# 参考

https://scalac.io/blog/user-authentication-keycloak-1/
https://blog.logrocket.com/implement-keycloak-authentication-react/

22
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?