0
1

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.

Auth0 パーミッション設定方法

Last updated at Posted at 2023-08-17

今回やりたいこと
・フロントがAuth0でログインした後に取得トークンを持ってAPIにリクエストする。
 API側ではトークンの権限(パーミッション)有無を確認してOKであればユーザー情報を返すようにする。
image1.png

以下を参考にさせて頂きました🙇
https://qiita.com/kourin1996/items/7b79d868de5c126d01d3

Auth0とは
・認証・認可プラットフォーム。(IDaaS(Identity as a Service))​
・ID/パスワード、シングルサインオン、MFA認証などがある。

Auth0設定
[フロントエンド設定]
・Auth0画面からApplicationsApplicationsCreate ApplicationCreateを行う。
image2.png
・作成後、Allowed Callback URLs,Allowed Logout URLs,Allowd Web Origins,Allowd Orgins(CORS)にフロントエンドURLをセット(http://localhost:3000)

[API設定]
・Auth0画面からApplicationsAPIsCreate APICreateを行う。(NameAPI,Identifierhttp://localhost:8000を併せて登録)
image3.png

[ユーザー設定]
・Auth0画面からUser ManagementusersCreate UserCreateを行う。(Email,Passwordを併せて登録)
image4.png

[Role設定]
・Auth0画面からApplicationsApplicationsAPIs⇒ 対象API選択 ⇒ Permissionでパーミッションを追加する
(ここでは仮にread:appointment)
a.png

併せてSettinghsタブでEnable RBAC,Add Permissons in the Access Tokenをオンにする
b.png

・Auth0画面からUser ManagementRolesCreate RoleCreateを行う。(Name,descriptionを併せて登録)
image5.png
・作成したRoleを表示⇒permissionsタブを選択⇒Add Permissionsにて対象APIを選択。
image6.png
・対象Permissionを選択して登録。
image7.png
usersタブを選択⇒Add Usersにて対象ユーザーをRoleに紐づける。
image10.png

Auth0の設定は以上となります。
あとは以下のAPI、フロントエンドソースの実装となります。

API実装
・golangによる実装

>mkdir authzero_server
>cd authzero_server
>go mod init authzero_sample

authzero_serverに以下のmain.goを作成。
authZeroDomain,claims["sub"]をご自身のものとしてください

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/auth0/go-jwt-middleware"
	"github.com/form3tech-oss/jwt-go"
	"github.com/pkg/errors"
	"github.com/rs/cors"
	"net/http"
	"time"
)

const (
	authZeroDomain      = "xxxxx.us.auth0.com ※ApplicationのDomain"
	getPublicKeyTimeOut = time.Second * 30
)

type JsonWebKey struct {
	Kty string   `json:"kty"`
	Kid string   `json:"kid"`
	Use string   `json:"use"`
	N   string   `json:"n"`
	E   string   `json:"e"`
	X5c []string `json:"x5c"`
}

type JsonWebKeys struct {
	Keys []JsonWebKey `json:"keys"`
}

var jwtMiddleware *jwtmiddleware.JWTMiddleware

func main() {

	// 公開鍵取得
	publicKey, err := getPublicKey()
	if err != nil {
		panic(err)
	}

	// jwtMiddleware生成
	jwtMiddleware = jwtmiddleware.New(jwtmiddleware.Options{
		ValidationKeyGetter: validateToken(publicKey),
		SigningMethod:       jwt.SigningMethodRS256,
		ErrorHandler:        func(w http.ResponseWriter, r *http.Request, err string) {},
	})

	// CORSセット
	c := cors.New(cors.Options{
		AllowedOrigins:   []string{"http://localhost:3000"},
		AllowedHeaders:   []string{"Authorization", "Content-Type"},
		AllowCredentials: true,
	})

	corsHandler := c.Handler(&Handler{})

	http.Handle("/getuser", corsHandler)

	// リッスン状態
	http.ListenAndServe(":8000", nil)
}

type Handler struct {
}

func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if err := jwtMiddleware.CheckJWT(w, r); err != nil {
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	}
	val := r.Context().Value("user")
	token, ok := val.(*jwt.Token)
	if !ok {
		http.Error(w, "error", http.StatusInternalServerError)
		return
	}
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		http.Error(w, "error", http.StatusInternalServerError)
		return
	}
	if !isHavePermission(claims) {
		http.Error(w, "don't have permission", http.StatusUnauthorized)
		return
	}
	if claims["sub"] != "auth0|xxxxxxxxxxxxxxxxx ※user_idの値" {
		http.Error(w, "error", http.StatusNotFound)
		return
	}
	// ユーザー情報を返す
	w.WriteHeader(200)
	w.Write([]byte(`{"name": "テスト太郎", "age": 20}`))
	return
}

func getPublicKey() (*JsonWebKeys, error) {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, getPublicKeyTimeOut)
	defer cancel()
	req, err := http.NewRequest(
		http.MethodGet,
		fmt.Sprintf("https://%s/.well-known/jwks.json", authZeroDomain),
		nil,
	)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req.WithContext(ctx))
	if err != nil {
		return nil, errors.WithStack(err)
	}
	defer resp.Body.Close()

	out := &JsonWebKeys{}
	if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
		return nil, errors.WithStack(err)
	}
	return out, nil
}

func validateToken(publicKeys *JsonWebKeys) func(*jwt.Token) (interface{}, error) {
	return func(token *jwt.Token) (interface{}, error) {
		// 公開鍵作成
		cert, err := createCert(publicKeys, token)
		if err != nil {
			return nil, err
		}
		return jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
	}
}

func createCert(jsonWebKeys *JsonWebKeys, token *jwt.Token) (string, error) {
	cert := ""
	for _, publicKey := range jsonWebKeys.Keys {
		if token.Header["kid"] == publicKey.Kid {
			cert = "-----BEGIN CERTIFICATE-----\n" + publicKey.X5c[0] + "\n-----END CERTIFICATE-----"
			return cert, nil
		}
	}
	return "", errors.New("not found key kid")
}

func isHavePermission(
	claims jwt.MapClaims,
) bool {

	p, ok := claims["permissions"]
	if !ok {
		return false
	}
	permissions := p.([]interface{})
	var i int
	for i = 0; i < len(permissions); i++ {
		if permissions[i] == "read:appointment" {
			return true
		}
	}
	return false
}


フロントエンド実装
・reactによる実装

プロジェクト作成

>npx create-react-app authzero_client
>cd authzero_client
>npm install --save @auth0/auth0-react

index.jsを以下内容に更新
domain,clientIdをご自身のものとしてください

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Auth0Provider } from "@auth0/auth0-react";

ReactDOM.render(
  <React.StrictMode>
    <Auth0Provider
      domain="xxxxx.us.auth0.com ※ApplicationのDomain"
          clientId="xxxxx ※ApplicationのClient ID"
      authorizationParams={{
        audience: "http://localhost:8000",
        redirect_uri: "http://localhost:3000",
      }}
    >
      <App />
    </Auth0Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

App.jsを以下内容に更新

import { useState, useEffect } from "react";
import "./App.css";
import { useAuth0 } from "@auth0/auth0-react";

const API_URL = "http://localhost:8000";

const useAuth0Token = () => {
  const { isAuthenticated, user, getAccessTokenSilently } = useAuth0();
  const [accessToken, setAccessToken] = useState(null);

  useEffect(() => {
    const fetchToken = async () => {
      setAccessToken(await getAccessTokenSilently({}
      
      ));
    };

    if (isAuthenticated) {
      fetchToken();
    }
  }, [isAuthenticated, user?.sub]);

  return accessToken;
};

function App() {
  const { loginWithRedirect, isAuthenticated } = useAuth0();
  const token = useAuth0Token();
  const [me, setMe] = useState(null);
  const [error, setError] = useState(null);

  const onClickLogin = () => {
    loginWithRedirect({
      appState: {
        returnTo: "/profile",
      },
      authorizationParams: {
        prompt: "login",
      },
    });
  };

  const onClickCall = async () => {
    try {
      const res = await fetch(`${API_URL}/getuser`, {
        method: "GET",
        mode: "cors",
        headers: {
          "Authorization": `Bearer ${token}`,
          "Content-Type": "application/json",
        },
      });
      if (!res.ok) {
        throw new Error(res.statusText);
      }
      const me = await res.json();
      setError(null);
      setMe(me);
    } catch (error) {
      console.log("error", error);
      setError(error);
    }
  };

  return (
    <div className="App">
      <div style={{margin: "50px" }} >
        <button style={{margin: "10px" }} onClick={onClickLogin} disabled={isAuthenticated}>{isAuthenticated ? "logged in" : "login"}</button>
        <button style={{margin: "10px" }} onClick={onClickCall}>getUserInfomation</button>
        <p>user: {JSON.stringify(me)}</p>
        <p>{error ? error.toString() : ""}</p>
      </div>
    </div>
  );
}

export default App;

動作確認

フロントエンド起動

>npm run start

API起動
c.png

loginボタンでAuth0ログイン

a.png

ログインできるとトークンを取得できます
a.png

jwt.ioでトークンをデコードすると以下のようなイメージとなります。
b.png

getUserInformationボタンでユーザー情報取得
b.png

⇒ユーザー情報取得リクエスト時、APIのisHavePermission()でトークンにあるpermissionsを確認してアクセス不可を判定して結果を返します🎉

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?