Graph Identities
The purpose of this technote is to demonstrate queries/mutations for identifying an actor attempting to use the graph.
This article demonstrates queries and mutations for identifying an actor attempting to use the graph. These examples can be useful for inspiring how the actor identity may be described in a GraphQL Schema and includes multiple use-cases for both users and services.
Our use-case identifies authenticated users and services as well as anonymous users.
First we define interfaces for both query and mutation identity types.
1interface Identity {
2 "An ID we can use to identify the session."
3 id: ID!
4}
5
6interface MutableIdentity {
7 "An ID we can use to identify the session."
8 id: ID!
9}
Then we create concrete types of these interfaces for our authenticated and unauthenticated identities.
1type AnonymousIdentity implements Identity @key(fields: "id") {
2 "An ID we can use to identify the session."
3 id: ID!
4}
5
6type UserIdentity implements Identity @key(fields: "id") {
7 "An ID we can use to identify the session."
8 id: ID!
9 "A reference to the authenticated user entity."
10 user: User
11}
12
13type ServiceIdentity implements Identity @key(fields: "id") {
14 "An ID we can use to identify the session."
15 id: ID!
16 "A reference to the authenticated service entity."
17 service: Service
18}
As well as an authentication error response.
1type NotAuthenticatedError {
2 message: String
3}
Next, we will construct our query type and response unions. We have selected unions here so that we are able to return both identities and errors as response data.
Nullability becomes important here; there is nuance around how nullability affects the way in which clients will need to handle failures.
1union AuthIdentity = UserIdentity | ServiceIdentity | NotAuthenticatedError
2union CurrentUser = UserIdentity | AnonymousIdentity
3
4type Query {
5 "A query which returns an authenticated user or service, or an anonymous identity."
6 session: Identity!
7 "A query which returns the currently authenticated identity or an error."
8 whoami: AuthIdentity!
9 "A query which returns the currently authenticated user identity, or an anonymous identity."
10 me: CurrentUser!
11}
Then by separating the mutation and query identities we are able to separate:
Actions that can be performed on identities.
Queries which can be performed on identities.
1type MutableAnonymousIdentity implements MutableIdentity @key(fields: "id") {
2 id: ID!
3 authenticateWithEmailAndPassword(
4 credentials: EmailAndPasswordCredentials!
5 ): UserIdentity!
6}
7
8type MutableUserIdentity implements MutableIdentity @key(fields: "id") {
9 id: ID!
10 resetPassword(credentials: ResetPasswordCredentials!): UserIdentity!
11}
12
13type MutableServiceIdentity implements MutableIdentity @key(fields: "id") {
14 id: ID!
15 service: MutableService!
16}
17
18union MutableCurrentUser = MutableUserIdentity | NotAuthenticatedError
19
20type Mutation {
21 session: MutableIdentity!
22 me: MutableCurrentUser!
23}
Finally, operations using the previously defines schema would look like the following.
1query CurrentAuth {
2 session {
3 id
4 }
5 whoami {
6 id
7 }
8 me {
9 id
10 }
11}
12
13mutation Login($credentials: EmailAndPasswordCredentials!) {
14 session {
15 ... on MutableAnonymousIdentity {
16 authenticateWithEmailAndPassword(credentials: $credentials) {
17 user {
18 ...
19 }
20 }
21 }
22 }
23}
24
25mutation ResetPassword($credentials: ResetPasswordCredentials!) {
26 me {
27 ... on MutableUserIdentity {
28 resetPassword(credentials: $credentials) {
29 user {
30 ...
31 }
32 }
33 }
34 }
35}