Authentication
Unless all of the data you are loading is completely public, your app has some sort of users, accounts and permissions systems. If different users have different permissions in your application, then you need a way to tell the server which user is associated with each request.
Apollo Client uses the ultra flexible Apollo Link that includes several options for authentication.
Cookie
If your app is browser based and you are using cookies for login and session management with a backend, it's very easy to tell your network interface to send the cookie along with every request. You just need to pass the credentials option. e.g. credentials: 'same-origin'
as shown below, if your backend server is the same domain or else credentials: 'include'
if your backend is a different domain.
1const link = createHttpLink({
2 uri: '/graphql',
3 credentials: 'same-origin'
4});
5
6const client = new ApolloClient({
7 cache: new InMemoryCache(),
8 link,
9});
This option is simply passed through to the fetch
implementation used by the HttpLink when sending the query.
Note: the backend must also allow credentials from the requested origin. e.g. if using the popular 'cors' package from npm in node.js, the following settings would work in tandem with the above apollo client settings:
1// enable cors
2var corsOptions = {
3 origin: '<insert uri of front-end domain>',
4 credentials: true // <-- REQUIRED backend setting
5};
6app.use(cors(corsOptions));
Header
Another common way to identify yourself when using HTTP is to send along an authorization header. It's easy to add an authorization
header to every HTTP request by chaining together Apollo Links. In this example, we'll pull the login token from localStorage
every time a request is sent:
1import { ApolloClient } from 'apollo-client';
2import { createHttpLink } from 'apollo-link-http';
3import { setContext } from 'apollo-link-context';
4import { InMemoryCache } from 'apollo-cache-inmemory';
5
6const httpLink = createHttpLink({
7 uri: '/graphql',
8});
9
10const authLink = setContext((_, { headers }) => {
11 // get the authentication token from local storage if it exists
12 const token = localStorage.getItem('token');
13 // return the headers to the context so httpLink can read them
14 return {
15 headers: {
16 ...headers,
17 authorization: token ? `Bearer ${token}` : "",
18 }
19 }
20});
21
22const client = new ApolloClient({
23 link: authLink.concat(httpLink),
24 cache: new InMemoryCache()
25});
Note that the above example is using ApolloClient
from the apollo-client
package. Headers can still be modified using ApolloClient
from the apollo-boost
package, but since apollo-boost
doesn't allow the HttpLink
instance it uses to be modified, headers have to be passed in as a config parameter:
1import ApolloClient from 'apollo-boost'
2
3const client = new ApolloClient({
4 request: (operation) => {
5 const token = localStorage.getItem('token')
6 operation.setContext({
7 headers: {
8 authorization: token ? `Bearer ${token}` : ''
9 }
10 })
11 }
12})
13
In this particular example headers are set individually for every request but you can also use headers
option. See the Apollo Boost Configuration options section for more details.
The server can use that header to authenticate the user and attach it to the GraphQL execution context, so resolvers can modify their behavior based on a user's role and permissions.
Reset store on logout
Since Apollo caches all of your query results, it's important to get rid of them when the login state changes.
The easiest way to ensure that the UI and store state reflects the current user's permissions is to call client.resetStore()
after your login or logout process has completed. This will cause the store to be cleared and all active queries to be refetched. If you just want the store to be cleared and don't want to refetch active queries, use client.clearStore()
instead. Another option is to reload the page, which will have a similar effect.
1const PROFILE_QUERY = gql`
2 query CurrentUserForLayout {
3 currentUser {
4 login
5 avatar_url
6 }
7 }
8`;
9
10function Profile() {
11 const { client, loading, data: { currentUser } } = useQuery(
12 PROFILE_QUERY,
13 { fetchPolicy: "network-only" }
14 );
15
16 if (loading) {
17 return <p className="navbar-text navbar-right">Loading...</p>;
18 }
19
20 if (currentUser) {
21 return (
22 <span>
23 <p className="navbar-text navbar-right">
24 {currentUser.login}
25
26 <button
27 onClick={() => {
28 // call your auth logout code then reset store
29 App.logout().then(() => client.resetStore());
30 }}
31 >
32 Log out
33 </button>
34 </p>
35 </span>
36 );
37 }
38
39 return (
40 <p className="navbar-text navbar-right">
41 <a href="/login/github">Log in with GitHub</a>
42 </p>
43 );
44}
1const PROFILE_QUERY = gql`
2 query CurrentUserForLayout {
3 currentUser {
4 login
5 avatar_url
6 }
7 }
8`;
9
10const Profile = () => (
11 <Query query={PROFILE_QUERY} fetchPolicy="network-only">
12 {({ client, loading, data: { currentUser } }) => {
13 if (loading) {
14 return <p className="navbar-text navbar-right">Loading...</p>;
15 }
16 if (currentUser) {
17 return (
18 <span>
19 <p className="navbar-text navbar-right">
20 {currentUser.login}
21
22 <button
23 onClick={() => {
24 // call your auth logout code then reset store
25 App.logout().then(() => client.resetStore());
26 }}
27 >
28 Log out
29 </button>
30 </p>
31 </span>
32 );
33 }
34 return (
35 <p className="navbar-text navbar-right">
36 <a href="/login/github">Log in with GitHub</a>
37 </p>
38 );
39 }}
40 </Query>
41);