Authentication allows you to know who is the current user, to log user in and to
register new users. There are two main parts of authentication system. Reindex
request may contain a JSON Web Token (JWT), that holds the credentials of the
user making the request. In addition, Reindex provides a way to authenticate
new or existing users via one of social login providers (e.g. Google), so that
user can receive an appropriate JWT. Such users are also stored as an object
of User
type.
JWT can be used without social login and even without the User
type. For
instance, an admin token that you have received is a JWT that does not include
a user ID, but just a flag that tells the user is an admin instead. Similarly,
you can generate your own tokens if you’d want to integrate custom login system
with Reindex.
Every request to Reindex GraphQL API may contain a Authorization
header with a
JSON Web Token inside. JSON Web Token is a JSON object, that
is Base64 encoded and cryptographically signed by a secret key. Reindex can
verify the signature and see that the token is valid. Reindex will then use the
information passed in token for authentication.
We support sub
(Subject), isAdmin
, exp
(Expiration Time) and iat
(Issued At) claims.
If sub
is present it must be a valid ID of User
object. isAdmin
is a
boolean and determines if a user has admin permissions. Both exp
and iat
is
a number of seconds since Unix epoch.
The secret key that is used to sign the token is stored in an object of type
ReindexSecret
. createReindexSecret
and deleteReindexSecret
allow you to
create new secret keys and delete existing ones. There is currently no way to
choose what secret is used for signing, so unless you generate the token
manually, there is little reason to have more than one secret.
You may manually create JWT with the secret using one of the JWT libraries or
even through jwt.io website. You should use HS256
as a
signing algorithm.
Reindex provides a built-in facility to log user in, using one of the supported authentication providers. Supported providers are:
Using those providers requires configuring them at the provider’s website.
Refer to the documentation of each authentication provider for details on how to
create an app in their system. When you’re asked to provide a callback URL you
should use https://NAME-OF-YOUR-APP.myreindex.com/auth/PROVIDER_NAME
.
After the configuration you should get two IDs, client or app ID and client or
app secret. This configuration should be stored in Reindex
ReindexAuthenticationProvider
type. You can create one, for example for
Google, with the following GraphQL query:
mutation {
createReindexAuthenticationProvider(input: {
type: google,
clientId: "YOUR-GOOGLE-CLIENT-ID",
clientSecret: "YOUR-GOOGLE-CLIENT-SECRET",
isEnabled: true
}) {
changedReindexAuthenticationProvider {
clientId,
id,
clientSecret,
type,
isEnabled
}
}
}
Tutorial has a detailed guide on how to enable Google authentication.
After the provider is configured, users can login using a selected provider. The
logic of cross-domain OAuth communication is pretty complicated, but
reindex-js
provides a shortcut. Refer to
reindex-js documentation.
When a user authenticates using one of the social login providers, the app will
obtain an access token that can be used to securely make requests to the API of
the provider. The access token is stored in the accessToken
field of the user
credentials object and you can use it to request more data in your own
server-side code, for example Facebook friends or GitHub repositories.
In order to fetch additional data, you may need to ask additional permissions
(OAuth scopes) during the authentication. These scopes can be configured using
the scopes
field of ReindexAuthenticationProvider
, which takes a list of
scope strings. If not defined, default scopes for each provider will be used.
You can find the list of supported scopes from the developer documentation of each provider:
["email"]
.["user:email"]
.["profile", "email"]
.(Twitter provider does not have custom scopes.)
For example, to configure Facebook login to request the user_likes
permission
in addition to the email
permission (included by default) set up a
ReindexAuthenticationProvider
object like this:
{
type: facebook,
clientId: "YOUR-FACEBOOK-APP-ID",
clientSecret: "YOUR-FACEBOOK-APP-SECRET",
isEnabled: true,
scopes: ["email", "user_likes"]
}
Auth0 also provides an embeddable login form Lock
that makes it easy to add a polished login experience to any web or native app.
You can use Lock for authenticating with Reindex using the loginWithToken
mutation that takes an Auth0 id_token
as an input and returns a Reindex token
for a signed in user.
To set up login with Lock, first create an authentication provider with your Auth0 credentials (in GraphiQL):
mutation {
createReindexAuthenticationProvider(input: {
type: auth0,
clientId: "AUTH0-CLIENT-ID",
clientSecret: "AUTH0-CLIENT-SECRET",
domain: "AUTH0-DOMAIN",
isEnabled: true,
}) {
id
}
}
Then add Lock in your app and call loginWithToken
in the callback function for
Lock:
const reindex = new Reindex(reindexURL);
const lock = new Auth0Lock(auth0ClientID, auth0Domain);
lock.show((error, profile, id_token) => {
reindex.loginWithToken('auth0', id_token);
});
That’s all! You can now enable any Auth0 connection and your users can login to your Reindex app with it. And as with other Reindex authentication providers, the user profile in Reindex is automatically updated on every login from Auth0.
Social login stores the data about the user inside User
nodes. The
field credentials
contains fields with a corresponding provider name (
google
, facebook
, twitter
, github
) and information received from the
provider. Credentials can only be read by the user themself or admins.
If you want to display some of this information to other users, you
should copy it to some other user field.
A new user is created on the first time the user signs in using a provider. After that the same user object is used.
Unlike other built-in types, you can extend User
type and thus it’s included
in your schema. It’s not possible to delete the User
type, though.
Very often you would want to display some user information, like their username
and email, that are available from the social login providers. Credentials are
only readable by the user, so you need to copy that data to some of your defined
fields. Let’s assume that the user type has fields username
and avatar
and
we want to copy them from one of the user’s social login profiles.
import Relay from 'react-relay'
export default class UpdateUserInfoMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User {
id
credentials {
github {
displayName
picture
}
google {
displayName
picture(size: 73)
}
facebook {
displayName
picture(height: 73, width: 73)
}
twitter {
displayName
picture(size: bigger)
}
auth0 {
displayName
picture
}
}
}
`
};
getMutation() {
return Relay.QL`mutation { updateUser }`
}
getVariables() {
const credentials = normalizeCredentials(this.props.user)
return {
id: this.props.user.id,
username: credentials.displayName,
avatarUrl: credentials.picture,
}
}
getFatQuery() {
return Relay.QL`
fragment on _UserPayload {
changedUser {
username
avatarUrl
}
}
`
}
getConfigs() {
return [
{
type: 'FIELDS_CHANGE',
fieldIDs: {
changedUser: this.props.user.id,
},
},
]
}
getOptimisticResponse() {
const credentials = normalizeCredentials(this.props.user)
return {
changedUser: {
username: credentials.displayName,
avatarUrl: credentials.picture,
}
}
}
}
function normalizeCredentials(user) {
for (const provider of [
'github', 'google', 'facebook', 'twitter', 'auth0'
]) {
if (user.credentials[provider]) {
return user.credentials[provider]
}
}
}
You can run this mutation in, for example, componentDidMount
of your root
component.
profile
contains
displayName
and picture
.const updateResult = await reindex.query(`
mutation($input: _UpdateUserInput!) {
updateUser(input: $input) {
id
}
}
`, {
input: {
id: reindexUserId,
username: profile.displayName,
avatarUrl: profile.picture,
},
});
for (const error of updateResult.errors || []) {
console.error(error);
}