More Related Content
Similar to React(TypeScript) + Go + Auth0 で実現する管理画面 (20)
React(TypeScript) + Go + Auth0 で実現する管理画面
- 8. やらなければいけないこと
Auth0の設定
1. Applicationの作成
2. APIの作成
3. Permissionの追加
4. Roleの作成
5. Roleの付与
フロントエンド(React)の実装
1. 認証していなければ Auth0のログインページへリダイレクト
2. API呼び出し時にアクセストークンを取得し Authorizationヘッダに設定
サーバーサイド(Go)の実装
1. Auth0テナント公開鍵の取得
2. 公開鍵によるJWTの検証
3. アプリケーションごとの検証
8
2.API呼び出し
(+トークン)
1.認証 トークン
公開鍵取得
3.検証(認可)
App
Auth0
- 9. やらなければいけないこと
Auth0の設定
1. Applicationの作成
2. APIの作成
3. Permissionの追加
4. Roleの作成
5. Roleの付与
フロントエンド(React)の実装
1. 認証していなければ Auth0のログインページへリダイレクト
2. API呼び出し時にアクセストークンを取得し Authorizationヘッダに設定
サーバーサイド(Go)の実装
1. Auth0テナント公開鍵の取得
2. 公開鍵によるJWTの検証
3. アプリケーションごとの検証
9
2.API呼び出し
(+トークン)
1.認証 トークン
公開鍵取得
3.検証(認可)
App
Auth0
- 13. Auth0の設定
2. APIの作成
APIs -> Settings
- (必要なら)アクセストークンの
有効期限変更
- デフォルトは24h
- RBAC(Role Based Access Control)を有
効
- これをONにしないとユーザーのRole
に基づいて認可されない
13
※2020/01/22時点での設定画面・デフォルト設定をもとにして
います
- 18. やらなければいけないこと
Auth0の設定
1. Applicationの作成
2. APIの作成
3. Permissionの追加
4. Roleの作成
5. Roleの付与
フロントエンド(React)の実装
1. 認証していなければ Auth0のログインページへリダイレクト
2. API呼び出し時にアクセストークンを取得し Authorizationヘッダに設定
サーバーサイド(Go)の実装
1. Auth0テナント公開鍵の取得
2. 公開鍵によるJWTの検証
3. アプリケーションごとの検証
18
2.API呼び出し
(+トークン)
1.認証 トークン
公開鍵取得
3.検証(認可)
App
Auth0
- 20. Reactの実装
import { Auth0Provider } from "@auth0/auth0-react";
// ...
render(
<Auth0Provider
domain={auth0Config.domain}
clientId={auth0Config.clientId}
audience={auth0Config.audience}
scope={auth0Config.scope}
redirectUri={window.location.origin}
>
<DimmingLoaderProvider>
<AuthController>
<App />
</AuthController>
</DimmingLoaderProvider>
</Auth0Provider>,
document.body.querySelector("#root")
);
20
Auth0Providerの設定
- domain, clientId
Applications
-> Settings
-> Basic Information に記載
- audience
APIの識別子
- scope
APIの要求するPermission
- redirectUri
認証後にリダイレクトするURL
→ 現在のURL
@auth0/auth0-react: 1.2.0
- 21. Reactの実装
21
import { useAuth0 } from "@auth0/auth0-react";
// ...
const AuthController: React.FC = ({ children }) => {
const loader = useLoader();
const {
isAuthenticated, isLoading, error,
loginWithRedirect, logout, getAccessTokenSilently,
} = useAuth0();
useEffect(() => {
loader.displayLoader(isLoading);
}, [loader, isLoading]);
useEffect(() => {
configureAuthFunc({ getAccessTokenSilently, logout });
}, [getAccessTokenSilently, logout]);
if (isLoading) {
return null;
}
if (!isAuthenticated) {
loginWithRedirect();
return null;
}
if (error) {
return <エラー表示 />;
}
return {children};
};
認証関連処理の実装
- isLoading = falseになるまで
待機
- isAuthenticated = falseなら
ログイン画面にリダイレクト
- isAuthenticated = trueなら
画面を表示
1. 認証していなければ
Auth0のログインページへ
リダイレクト
API呼び出しで使えるように
設定
@auth0/auth0-react: 1.2.0
- 22. Reactの実装
22
export const fetchWithAuthz = async (
apiPath: string,
apiConfig: RequestInit
) => {
try {
const token = await getAccessTokenSilently();
apiConfig.headers = {
...apiConfig.headers,
Authorization: `Bearer ${token}`,
};
} catch (e) {
return new Failure(e, "auth error");
}
let resp: Response;
try {
resp = await fetch(apiPath, apiConfig);
} catch (e) {
return new Failure(e, "unknown error");
}
// …
}
API呼び出しの実装例
- getAccessTokenSilently()は呼び
出し時に必ず呼ぶ
- トークンのキャッシュなどは内
部でやってくれる
2. API呼び出し時にアクセス
トークンを取得し、
Authorizationヘッダに設定
@auth0/auth0-react: 1.2.0
- 23. やらなければいけないこと
Auth0の設定
1. Applicationの作成
2. APIの作成
3. Permissionの追加
4. Roleの作成
5. Roleの付与
フロントエンド(React)の実装
1. 認証していなければ Auth0のログインページへリダイレクト
2. API呼び出し時にアクセストークンを取得し Authorizationヘッダに設定
サーバーサイド(Go)の実装
1. Auth0テナント公開鍵の取得
2. 公開鍵によるJWTの検証
3. アプリケーションごとの検証
23
2.API呼び出し
(+トークン)
1.認証 トークン
公開鍵取得
3.検証(認可)
App
Auth0
- 25. Goの実装
ルーティング層
r := chi.NewRouter()
//...
r.Route("/admin", func(r chi.Router) {
//...
r.Route("/api", func(r chi.Router) {
r.Use(auth0util.Auth0Middleware(appConfig.Auth0Config))
//各APIのルーティングの記載
})
})
//...
25
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
- 26. Goの実装
Middleware実装①
- go-jwt-middlewareは
auth0のライブラリだが、汎
用的
→ それなりに実装が必要
26
公開鍵取得 URL
import (
//(省略)
jwtmiddleware "github.com/auth0/go-jwt-middleware"
"github.com/form3tech-oss/jwt-go"
)
const ContextTokenKey = "Token"
func Auth0Middleware(auth0Config *config.Auth0Config) func(next
http.Handler) http.Handler {
middleware := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
cert, err := getPemCert(auth0Config.PemCertURL, token)
if err != nil {
return nil, errors.Wrap(err, "公開鍵の取得")
}
result, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
if err != nil {
return nil, errors.Wrap(err, "公開鍵のパース")
}
return result, nil
},
UserProperty: ContextTokenKey,
SigningMethod: jwt.SigningMethodRS256,
})
return func(next http.Handler) http.Handler { /* 検証処理(後述)*/ }
}
検証時、Contextに
このKeyでトークン情報が保存される
公開鍵取得 URL
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
1. Auth0テナント
公開鍵の取得
- 27. Goの実装
27
func getPemCert(pemURL string, token *jwt.Token) (string, error) {
resp, err := http.Get(pemURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
var jwks = Jwks{}
if err = json.NewDecoder(resp.Body).Decode(&jwks); err != nil {
return "", err
}
cert := ""
for k := range jwks.Keys {
if token.Header["kid"] == jwks.Keys[k].Kid {
cert = "-----BEGIN CERTIFICATE-----n" + jwks.Keys[k].X5c[0] +
"n-----END CERTIFICATE-----"
}
}
if cert == "" {
return "", errors.New("鍵が見つかりません")
}
return cert, nil
}
Middleware実装②
- 「1.Auth0テナント公開鍵」の
取得の中身
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
- 29. Goの実装
29
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := middleware.CheckJWT(w, r); err != nil {
w.WriteHeader(http.StatusForbidden)
return
}
token := r.Context().Value(ContextTokenKey)
claims := token.(*jwt.Token).Claims.(jwt.MapClaims)
if !verifyAccessTokenClaims(
r.Context(), claims,
auth0Config.Issuer,
auth0Config.Audience,
auth0Config.Scope) {
w.WriteHeader(http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
Auth0のテナントドメイン URL(Issuer)
要求するAPI(Audience)
要求するPermission(Scope)
UserPropertyで指定した
Keyでトークン情報取得
2. 公開鍵による
JWTの検証
Middleware実装③
- 検証処理
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
3. アプリケーション
ごとの検証
- 30. Goの実装
30
Middleware実装④
- 「3.クレームの検証」の
中身
func verifyAccessTokenClaims(ctx context.Context, claims jwt.MapClaims,
expIss, expAud, expScope string) bool {
logger := logging.GetLogger(ctx)
if !claims.VerifyIssuer(expIss, true) {
logger.Warn("Issuerが%sではありません", expIss)
return false
}
audience, ok := claims["aud"].([]interface{})
if !ok {
logger.Warn("audクレームがありません")
return false
}
verifyAudience := func() bool {
for _, a := range audience {
if a.(string) == expAud {
return true
}
}
return false
}
if !verifyAudience() {
logger.Warn("Audienceに%sが含まれません", expAud)
return false
}
//次ページへ
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
jwt.MapClaims.VerifyAudience()は
[]stringにキャストしようとして失敗する
ので、自前で検証
ドキュメントの実装だと
issuer,audienceの検証は
ValidationKeyGetter内でやっているが、凝
集度的にはここでやるほうが自然だと思う ...
- 31. Goの実装
31
//つづき
scope, ok := claims["scope"].(string)
if !ok {
logger.Warn("scopeクレームがありません")
return false
}
verifyScope := func() bool {
for _, s := range strings.Split(scope, " ") {
if s == expScope {
return true
}
}
return false
}
if !verifyScope() {
logger.Warn("Scopeに%sが含まれません", expScope)
return false
}
return true
}
Middleware実装④
- 「3.クレームの検証」の
中身
以下を参考に実装
Auth0 Docs
Go Authorization
Create a middleware to validate Access
Tokens
https://auth0.com/docs/quickstart/backend/go
lang/01-authorization#validate-access-tokens
github.com/auth0/go-jwt-middleware v1.0.0
github.com/form3tech-oss/jwt-go v3.2.2
github.com/go-chi/chi v4.1.2+incompatible
- 32. まとめ
- React(TypeScript) + Goでの
管理画面の認可におけるAuth0の導入方法を紹介
- 設定項目さえ把握できれば導入は容易
- フロントエンドの実装は軽微
- サーバーサイドの実装は再利用できることを考えると、
2つ目以降のアプリケーションでは認証・認可の実装はほとんど
不要
32
- 35. Auth0の設定
5. Rule
- Role自動付与したい場合に利用
- ユーザーサインアップ時の
イベントをフックして処理を
する
- 社内のユーザーに特定のロール
を自動付与するケースの実装例
を紹介
35
async function addAdminRole(user, context, callback) {
const ManagementClient = require("auth0@2.30.0").ManagementClient;
if (!user.email || !user.email_verified) {
return callback(null, user, context);
}
const shouldAddRole = function (user, context) {
const assignedRoles = (context.authorization || {}).roles || [];
if (assignedRoles.includes("[付与すべき Role名]")) {
console.log(`${user.name} has already had default roles`);
return false;
}
const endsWith = "@[対象ドメイン ]";
if (
user.email &&
user.email.substring(
user.email.length - endsWith.length,
user.email.length
) === endsWith
) {
return true;
}
return false;
};
//次ページへ
メール確認が
済んでいるかチェック
ロールを付与すべきか判定
- 36. Auth0の設定
36
//give default roles if the user doesn't already have any roles assigned
if (shouldAddRole(user, context)) {
try {
const management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain,
});
await management.assignRolestoUser(
{ id: user.user_id },
{ roles: ["[付与すべき RoleのID"] }
);
console.log(`Default role has been assigned to ${user.name}.`);
} catch (ex) {
console.error("Failed to add default role to user", ex);
}
}
return callback(null, user, context);
}
ロールの付与
5. Rule
- Role自動付与したい場合に利用
- ユーザーサインアップ時の
イベントをフックして処理を
する
- 社内のユーザーに特定のロール
を自動付与するケースの実装例
を紹介