ReactおよびGraphQLアプリに認証とルート保護を適用する方法
序章
メール、Facebook、Google、Twitter、Githubはすべて、ウェブアプリでユーザーを認証するための可能なオプションです。 ReactとGraphQLで構築されたアプリは、そのような認証の候補でもあります。
この記事では、以下を使用してさまざまな認証プロバイダーをGraphQLアプリに追加する方法を学習します。
- GraphQL :APIのクエリ言語
- Graphcool :サービスとしてのGraphQLバックエンド
- Auth0 :サービスとしての認証
- React :ユーザーインターフェースを構築するためのJavaScriptライブラリ
また、ユーザーが認証サーバーによる認証に失敗した場合にReactルートがアクセスされないように保護する方法についても学習します。
ステップ1—プロジェクトの準備
計画は、Reactプロジェクトをセットアップすることです。 Reactプロジェクトが設定されているのと同じディレクトリで、Graphcoolサーバーを構成し、サーバーの動作をプロジェクトに指示できます。 インストールから始めます create-react-app
と graphcool-framework
CLIツール:
- npm install -g create-react-app graphcool-framework
次に、ReactCLIツールを使用して新しいReactアプリの足場を作成します。
- create-react-app do-auth
作成したReactアプリに移動し、Graphcool initコマンドを実行して、Graphcoolサーバーを作成します。
- cd do-auth
- graphcool-framework init server
ステップ2—ReactルートとUIを作成する
パブリックルートと保護ルートの両方が必要です。
- ホームページ(公開)
- プロフィールページ(保護)
- 管理者ページ(保護され、管理者のみが利用可能)
- アバウトページ(公開)
作成する containers
フォルダと追加 home.js
, profile.js
, admin.js
、 と about.js
これらの各ルートを表すファイルとして。
Home.js
import React from 'react';
import Hero from '../components/hero';
const Home = (props) => (
<div>
<Hero page="Home"></Hero>
<h2>Home page</h2>
</div>
)
export default Home;
About.js
import React from 'react';
import Hero from '../components/hero';
const About = (props) => (
<div>
<Hero page="About"></Hero>
<h2>About page</h2>
</div>
)
export default About;
Profile.js
import React from 'react';
import Hero from '../components/hero';
const Profile = props => (
<div>
<Hero page="Profile"></Hero>
<h2>Profile page</h2>
</div>
);
export default Profile;
Admin.js
import React from 'react';
import Hero from '../components/hero';
const Admin = (props) => (
<div>
<Hero></Hero>
<Hero page="Admin"></Hero>
</div>
)
export default Admin;
各ページはインポートして使用します Hero
着陸ヒーローメッセージを表示します。 作成する components
フォルダを作成し、 Hero.js
次のファイル:
import React from 'react';
import Nav from './nav'
import './hero.css'
const Hero = ({ page }) => (
<section className="hero is-large is-dark">
<div className="hero-body">
<Nav></Nav>
<div className="container">
<h1 className="title">DO Auth</h1>
<h2 className="subtitle">Welcome to the Auth {page}</h2>
</div>
</div>
</section>
);
export default Hero;
ナビゲーションコンポーネントを次のように追加できます nav.js
コンポーネントフォルダにもあります。 その前に、Reactアプリのルーティングを設定し、ページをルートとして公開する必要があります。
ReactRouterライブラリのインストールから始めます。
- yarn add react-router-dom
次に、を介してアプリにルーターを提供します index.js
エントリファイル:
//...
import { BrowserRouter } from 'react-router-dom';
import App from './App';
//...
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
//...
次に、アプリコンポーネントでルートを構成します。
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import Profile from './containers/profile';
import About from './containers/about';
import Admin from './containers/admin';
import Home from './containers/home';
class App extends Component {
render() {
return (
<div className="App">
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/profile" component={Profile} />
<Route exact path="/admin" component={Admin} />
</Switch>
</div>
);
}
}
export default App;
ナビゲーションコンポーネントに戻る(nav.js
)、使用したい Link
からのコンポーネント react-router-dom
ナビゲーションをプロビジョニングするには:
import React from 'react';
import { Link } from 'react-router-dom'
import './nav.css'
const Nav = () => {
return (
<nav className="navbar">
<div className="navbar-brand">
<Link className="navbar-item" to="/">
<strong>Auth Page</strong>
</Link>
</div>
<div className="navbar-menu">
<div className="navbar-end">
<Link to="/about" className="navbar-item">
About
</Link>
<Link to="/profile" className="navbar-item">
Profile
</Link>
<div className="navbar-item join">
Join
</div>
</div>
</div>
</nav>
);
};
export default Nav;
次に、Graphcoolサーバーを作成します
- graphcool-framework deploy
ステップ3—サーバー認証用のAuth0の構成
クライアントアプリから少し離れて、前に作成したGraphcoolサーバーに戻りましょう。 Graphcoolには、サーバーの機能を拡張できるサーバーレス機能の概念があります。 この機能は、認証を含む多くのサードパーティ統合を実現するために使用できます。
このような統合の一部の関数は事前にパッケージ化されているため、最初から作成する必要はありません。 テンプレートをインストールし、いくつかの構成とタイプのコメントを外してから、必要に応じてコードを更新または微調整するだけです。
Auth0テンプレートを追加しましょう。 あなたがにいることを確認してください server
フォルダを作成し、次を実行します。
- graphcool-framework add-template graphcool/templates/auth/auth0
これにより、 auth0
のフォルダ server/src
. このフォルダーには、関数ロジック、タイプ、およびこの関数をトリガーするミューテーション定義の両方が含まれています。
次に、Auth0 APIを作成してから、APIの構成をサーバーに追加する必要があります。 最初にアカウントを作成してから、APIダッシュボードから新しいAPIを作成します。 APIには好きな名前を付けることができます。 既存のすべてのAPIに固有の識別子を提供します。
でテンプレート構成のコメントを解除します server/src/graphcool.yml
次のように更新します。
authenticate:
handler:
code:
src: ./src/auth0/auth0Authentication.js
environment:
AUTH0_DOMAIN: [YOUR AUTH0 DOMAIN]
AUTH0_API_IDENTIFIER: [YOUR AUTH0 IDENTIFIER]
type: resolver
schema: ./src/auth0/auth0Authentication.graphql
AUTH0_DOMAIN
と AUTH0_API_IDENTIFIER
に公開されます process.env
環境変数としてのあなたの機能で。
templateコマンドは、タイプインも生成します server/src/types.graphql
. デフォルトではコメントアウトされています。 次のコメントを解除する必要があります。
type User @model {
# Required system field:
id: ID! @isUnique # read-only (managed by Graphcool)
# Optional system fields (remove if not needed):
createdAt: DateTime! # read-only (managed by Graphcool)
updatedAt: DateTime! # read-only (managed by Graphcool)
email: String
auth0UserId: String @isUnique
}
を削除する必要があります User
この認証が行われるようにサーバーが作成されたときに生成されたタイプ User
タイプはそれを置き換えることができます。
次に、認証ロジックを微調整する必要があります。 次のコードブロックを見つけます。
jwt.verify(
token,
signingKey,
{
algorithms: ['RS256'],
audience: process.env.AUTH0_API_IDENTIFIER,
ignoreExpiration: false,
issuer: `https://${process.env.AUTH0_DOMAIN}/`
},
(err, decoded) => {
if (err) throw new Error(err)
return resolve(decoded)
}
)
そして、更新します audience
のプロパティ verify
方法 aud
:
jwt.verify(
token,
signingKey,
{
algorithms: ['RS256'],
aud: process.env.AUTH0_API_IDENTIFIER,
ignoreExpiration: false,
issuer: `https://${process.env.AUTH0_DOMAIN}/`
},
(err, decoded) => {
if (err) throw new Error(err)
return resolve(decoded)
}
)
最後に、認証トークンは常に電子メールをエンコードするため、電子メールを取得するために次のことを行う必要はありません。
let email = null
if (decodedToken.scope.includes('email')) {
email = await fetchAuth0Email(accessToken)
}
からメールを受け取ることができます decodedToken
すぐに:
const email = decodedToken.email
これにより、 fetchAuth0Email
機能が役に立たないので、削除できます。
次のコマンドを実行して、サーバーをGraphcoolにデプロイします。
- graphcool-framework
Graphcoolを初めて使用する場合は、Graphcoolアカウントを作成するためのページに移動する必要があります。
ステップ4—クライアント認証用のAuth0の構成
サーバーは、認証用のトークンを受信するように設定されています。 次のコマンドを実行して、Graphcoolプレイグラウンドでこれをテストできます。
- graphcool-framework playground
ミューテーションにAuth0トークンを提供し、Graphcoolからノードトークンを取得します。 Auth0からトークンを取得する方法を見てみましょう。
APIを作成するのと同じように、プロジェクトのクライアントも作成する必要があります。 クライアントは、ブラウザからの認証をトリガーするために使用されます。 Auth0ダッシュボードナビゲーションで、クライアントをクリックして、新しいクライアントを作成します。
アプリケーションタイプは、シングルページWebアプリケーションに設定する必要があります。これは、ルーティングされたReactアプリです。
authが開始されると、Auth0ドメインにリダイレクトされてユーザーを確認します。 ユーザーが確認されると、ユーザーをアプリにリダイレクトする必要があります。 コールバックURLは、リダイレクト後に戻る場所です。 作成したクライアントの設定タブに移動し、コールバックURLを設定します。
これで、Auth0ダッシュボードでのクライアント構成のセットアップは完了です。 次に実行したいのは、Reactアプリでいくつかのメソッドを公開するサービスを作成することです。 これらのメソッドは、認証のトリガー、Auth0からの応答の処理、ログアウトなどのユーティリティタスクを処理します。
まず、Auth0JSライブラリをインストールします。
- yarn add auth0-js
次に、を作成します services
のフォルダ src
. 追加します auth.js
次の内容の新しいフォルダにあります。
import auth0 from 'auth0-js';
export default class Auth {
auth0 = new auth0.WebAuth({
domain: '[Auth0 Domain]',
clientID: '[Auth0 Client ID]',
redirectUri: 'http://localhost:3000/callback',
audience: '[Auth0 Client Audience]',
responseType: 'token id_token',
scope: 'openid profile email'
});
handleAuthentication(cb) {
this.auth0.parseHash({hash: window.location.hash}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.auth0.client.userInfo(authResult.accessToken, (err, profile) => {
this.storeAuth0Cred(authResult, profile);
cb(false, {...authResult, ...profile})
});
} else if (err) {
console.log(err);
cb(true, err)
}
});
}
storeAuth0Cred(authResult, profile) {
// Set the time that the access token will expire at
let expiresAt = JSON.stringify(
authResult.expiresIn * 1000 + new Date().getTime()
);
localStorage.setItem('do_auth_access_token', authResult.accessToken);
localStorage.setItem('do_auth_id_token', authResult.idToken);
localStorage.setItem('do_auth_expires_at', expiresAt);
localStorage.setItem('do_auth_profile', JSON.stringify(profile));
}
storeGraphCoolCred(authResult) {
localStorage.setItem('do_auth_gcool_token', authResult.token);
localStorage.setItem('do_auth_gcool_id', authResult.id);
}
login() {
this.auth0.authorize();
}
logout(history) {
// Clear access token and ID token from local storage
localStorage.removeItem('do_auth_access_token');
localStorage.removeItem('do_auth_id_token');
localStorage.removeItem('do_auth_expires_at');
localStorage.removeItem('do_auth_profile');
localStorage.removeItem('do_auth_gcool_token');
localStorage.removeItem('do_auth_gcool_id');
// navigate to the home route
history.replace('/');
}
isAuthenticated() {
// Check whether the current time is past the
// access token's expiry time
const expiresAt = JSON.parse(localStorage.getItem('do_auth_expires_at'));
return new Date().getTime() < expiresAt;
}
getProfile() {
return JSON.parse(localStorage.getItem('do_auth_profile'));
}
}
このコードの機能は次のとおりです。
- まず、これによりAuth0 SDKのインスタンスが作成され、Auth0クライアントの資格情報を使用して構成されます。 このインスタンスは、インスタンス変数に格納されます。
auth0
. handleAuthentication
認証が完了すると、コンポーネントの1つから呼び出されます。 Auth0は、URLハッシュを介してトークンを返します。 このメソッドは、このハッシュを読み取って渡します。storeAuth0Cred
とstoreGraphCoolCred
将来の使用のために、資格情報をlocalStorageに保持します。- あなたは呼び出すことができます
isAuthenticated
localStorageに保存されているトークンがまだ有効かどうかを確認します。 getProfile
ユーザーのプロファイルのJSONペイロードを返します。
また、ユーザーが認証されている場合はユーザーをプロファイルページにリダイレクトし、認証されていない場合はユーザーをホームページ(デフォルトページ)に送り返します。 コールバックページはこれに最適な候補です。 まず、のルートに別のルートを追加します App.js
:
//...
import Home from './containers/home';
import Callback from './containers/callback'
class App extends Component {
render() {
return (
<div className="App">
<Switch>
<Route exact path="/" component={Home} />
{/* Callback route */}
<Route exact path="/callback" component={Callback} />
...
</Switch>
</div>
);
}
}
export default App;
/callback
を使用します Callback
作成する必要のあるコンポーネント src/container/Callback.js
:
import React from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import Auth from '../services/auth'
const auth = new Auth();
class Callback extends React.Component {
componentDidMount() {
auth.handleAuthentication(async (err, authResult) => {
// Failed. Send back home
if (err) this.props.history.push('/');
// Send mutation to Graphcool with idToken
// as the accessToken
const result = await this.props.authMutation({
variables: {
accessToken: authResult.idToken
}
});
// Save response to localStorage
auth.storeGraphCoolCred(result.data.authenticateUser);
// Redirect to profile page
this.props.history.push('/profile');
});
}
render() {
// Show a loading text while the app validates the user
return <div>Loading...</div>;
}
}
// Mutation query
const AUTH_MUTATION = gql`
mutation authMutation($accessToken: String!) {
authenticateUser(accessToken: $accessToken) {
id
token
}
}
`;
これは handleAuthentication
によって公開されたメソッド Auth
Graphcoolサーバーにミューテーションを送信します。 サーバーへの接続はまだ設定されていませんが、設定してユーザーの認証を試みると、このコールバックページは書き込みミューテーションをGraphcoolサーバーに送信して、ユーザーが存在し、リソースへのアクセスが許可されていることをサーバーに通知します。
ステップ5—Apolloのセットアップとサーバーへの接続
コールバックコンポーネントでは、 graphql
(まだインストールしていません)コンポーネントをミューテーションに接続します。 これは、Graphcoolサーバーへの接続がまだあることを意味するものではありません。 Apolloを使用してこの接続を設定してから、アプリのトップレベルでApolloインスタンスを提供する必要があります。
必要な依存関係のインストールから始めます。
- yarn add apollo-client-preset react-apollo graphql-tag graphql
を更新します src/index.js
エントリファイル:
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
// Import modules
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
// Create connection link
const httpLink = new HttpLink({ uri: '[SIMPLE API URL]' });
// Configure client with link
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache()
});
// Render App component with Apollo provider
ReactDOM.render(
<BrowserRouter>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</BrowserRouter>,
document.getElementById('root')
);
registerServiceWorker();
まず、これはすべての依存関係をインポートします。 次に、これはを使用してリンクを作成しました HttpLink
. 渡される引数は、URIを持つオブジェクトです。 サーバーフォルダで次のコマンドを実行すると、サーバーのURIを取得できます。
- graphcool-framework list
シンプルURIを使用して、上記のコードのプレースホルダーを置き換えます。
次に、このリンクとキャッシュを使用してApolloクライアントインスタンスを作成および構成しました。 この作成されたクライアントインスタンスは、プロップとしてApolloプロバイダーに渡されます。Apolloプロバイダーは App
成分。
ステップ6—認証フローのテスト
すべてが無傷であるため、ナビゲーションバーのボタンにイベントを追加して、認証プロセスをトリガーできます。
//...
import Auth from '../services/auth'
const auth = new Auth();
const Nav = () => {
return (
<nav className="navbar">
...
<div className="navbar-menu">
<div className="navbar-end">
...
<div className="navbar-item join" onClick={() => {auth.login()}}>
Join
</div>
</div>
</div>
</nav>
);
};
export default Nav;
[参加]ボタンをクリックすると、 auth.login
認証のためにAuth0ドメインにリダイレクトするメソッド。
ユーザーがログインした後、Auth0はユーザーが自分のプロファイル情報にアクセスするかどうかを尋ねます。
認証後、ページがに戻るのを確認します /callback
そしてその /profile
認証が成功した場合。
Graphcoolダッシュボードのデータビューに移動し、Usersテーブルを開くことで、ユーザーが作成されたことを確認することもできます。
ステップ7—条件付きボタンの追加
次に、非表示にします login
ユーザーが認証されたときにボタンを押し、代わりにログアウトボタンを表示します。 に戻る nav.js
交換してください Join
この条件付きロジックを持つボタン要素:
{auth.isAuthenticated() ? (
<div
className="navbar-item join"
onClick={() => {
auth.logout();
}}
>
Logout
</div>
) : (
<div
className="navbar-item join"
onClick={() => {
auth.login();
}}
>
Join
</div>
)}
The Auth
servicesは、と呼ばれるメソッドを公開します isAuthenticated
トークンがlocalStorageに保存されており、有効期限が切れていないかどうかを確認します。 その後、ユーザーがログインします。
ステップ8—ユーザーのプロファイルを表示する
Authサービスを使用して、ログインしているユーザープロファイルを取得することもできます。 このプロファイルは、localStorageですでに利用可能です。
//...
import Auth from '../services/auth';
const auth = new Auth();
const Profile = props => (
<div>
//...
<h2 className="title">Nickname: {auth.getProfile().nickname}</h2>
</div>
);
export default Profile;
ニックネームはブラウザに出力されます。
ステップ9—Graphcoolエンドポイントの保護
現時点では、トークンについてサーバーに通知していないため、認証されたユーザーは制限されたバックエンドに引き続きアクセスできます。
トークンを次のように送信できます Bearer
リクエストのヘッダーにあるトークン。 を更新します index.js
次のように:
//...
import { ApolloLink } from 'apollo-client-preset'
const httpLink = new HttpLink({ uri: '[SIMPLE URL]' });
const middlewareAuthLink = new ApolloLink((operation, forward) => {
const token = localStorage.getItem('do_auth_gcool_token')
const authorizationHeader = token ? `Bearer ${token}` : null
operation.setContext({
headers: {
authorization: authorizationHeader
}
})
return forward(operation)
})
const httpLinkWithAuthToken = middlewareAuthLink.concat(httpLink)
HTTPリンクだけでApolloクライアントを作成する代わりに、作成したミドルウェアを使用するように更新します。これにより、作成したすべてのサーバー要求にトークンが追加されます。
const client = new ApolloClient({
link: httpLinkWithAuthToken,
cache: new InMemoryCache()
})
その後、サーバー側のトークンを使用してリクエストを検証できます。
ステップ10—ルートを保護する
プロジェクトの最も重要な部分であるサーバーとそのデータを保護することで素晴らしい仕事をした限り、コンテンツがないルートにユーザーをぶら下げたままにしておくのは意味がありません。 The /profile
ユーザーが認証されていない場合は、ルートにアクセスできないように保護する必要があります。
アップデート App.js
ユーザーがプロファイルにアクセスしたが認証されていない場合にホームページにリダイレクトするには:
//...
import { Switch, Route, Redirect } from 'react-router-dom';
//...
import Auth from './services/auth';
const auth = new Auth();
class App extends Component {
render() {
return (
<div className="App">
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/callback" component={Callback} />
<Route exact path="/about" component={About} />
<Route
exact
path="/profile"
render={props =>
auth.isAuthenticated() ? (
<Profile />
) : (
<Redirect
to={{
pathname: '/'
}}
/>
)
}
/>
</Switch>
</div>
);
}
}
export default App;
あなたはまだ使用しています auth.isAuthenticated
認証を確認します。 trueが返された場合、 /profile
勝つ、そうでなければ、 /
勝ちます。
結論
このチュートリアルでは、GraphQLプロジェクトでAuth0を使用してユーザーを認証しました。 できることは、Auth0ダッシュボードに移動し、Twitterなどのソーシャル認証オプションをさらにいくつか追加することです。 Twitter開発者のWebサイトから取得できるTwitter開発者の資格情報の入力を求められます。