- Local Authentication
- Graphweaver Auth Package
- Environment Variables
- Creating an AuthResolver
- Adding the User to the Context
- Local Authentication Apollo Plugin
- Add User to Context
- Securing Requests Admin UI
- Testing Authentication
Local Authentication
Local authentication can be used to secure access to Graphweaver using a set of credentials stored in a local datastore.
Let’s start by covering the key components that we will use.
Graphweaver Auth Package
The @exogee/graphweaver-auth
package exports a number of properties that you need to understand before implementing authentication.
Let’s take a look at these:
AuthorizationContext
: TheAuthorizationContext
is a typescript type. It represents the context object that is passed to resolvers and hooks during the execution of GraphQL operations. It contains information about the authenticated user, including their roles and authentication token. TheAuthorizationContext
is used to implement authorization logic and make decisions based on the user's access permissions.localAuthApolloPlugin
:localAuthApolloPlugin
is a plugin for the Apollo Server, which is the underlying GraphQL server used by Graphweaver. This plugin enhances the server's functionality by adding support for local authentication. It hooks into the authentication process, allowing you to define custom logic for authenticating users and generating their authentication tokens.setAdministratorRoleName
:setAdministratorRoleName
is a function used to set the role name for the administrator user in the authentication system. You can use this function to customize the role name according to your application's needs. The administrator role typically has elevated privileges and permissions within the system.LocalAuthResolver
is a base resolver class that you can extend in your own resolver classes to implement local authentication functionality in your GraphQL API. This will add alogin
mutation to the GraphQL schema.
Next, let’s look at the steps we need to perform to get authentication working, which are:
- Creating an AuthResolver
- Creating a
addUserToContext
function that adds the logged in user details to the context - Optionally, restrict access to the admin-ui
But first let’s cover the environment variables that need to be created:
Environment Variables
There are two environment variables required when implementing local authentication:
LOCAL_AUTH_JWT_SECRET
: This environment variable represents the JWT (JSON Web Token) secret used for local authentication and is used to sign the token.LOCAL_AUTH_REDIRECT_URI
: This environment variable represents the redirect URI you would like users to be sent to when they are challenged for authentication.
Now with those setup let’s look at the AuthResolver.
Creating an AuthResolver
The AuthResolver is used to add a login
mutation to the schema. To create this resolver it is best to place the file in your ./src/schema
directory. The AuthResolver will look like this:
@Resolver()
export class AuthResolver extends LocalAuthResolver {
async authenticate(username: string, password: string) {
// Implement authentication logic here
}
}
As you can see above we have extended the AuthResolver
class with the LocalAuthResolver
. Yet, the implementation is not complete. In order to support authentication you must define an authenticate
function.
This function will be called when the login
mutation is executed. A valid authenticate
function will look like this:
async authenticate(username: string, password: string) {
const login = credentials.find(
(login) => login.username === username && login.password === password
);
if (!login) {
throw new Error('Unknown username or password, please try again');
}
const user = User.fromBackendEntity(
await BaseLoaders.loadOne({ gqlEntityType: User, id: login.id })
);
if (!user) {
throw new Error('Bad Request: Unknown user id provided.');
}
return mapUserToProfile(user);
}
In the example above we are checking the login username and password against some credentials and then fetching the User
data for the logged in user.
This will then be exchanged for a JWT token by the login mutation.
Now we have created this AuthResolver make sure to include it in the Graphweaver configuration:
const resolvers = [TaskResolver, TagResolver, UserResolver, AuthResolver];
In the example above we are adding it next to our other resolvers.
Next, we need to create a function that will return a User
, which is then added to the GraphQL context.
Adding the User to the Context
Now that we have the resolver setup let’s add the localAuthApolloPlugin
to the Graphweaver server.
Local Authentication Apollo Plugin
Next up we need to add the local authentication apollo plugin. This is what the Graphweaver configuration would look like with the plugin added:
import { localAuthApolloPlugin } from '@exogee/graphweaver-auth';
const graphweaver = new Graphweaver<AuthorizationContext>({
...
apolloServerOptions: {
introspection: isOffline,
plugins: [localAuthApolloPlugin(addUserToContext)],
},
...
});
As you can see from the above example we are adding the localAuthApolloPlugin
to the plugins section of the apolloServerOptions
.
There is also a addUserToContext
function.
Let’s look at this next and how it is used.
Add User to Context
The addUserToContext
is used to populate the AuthorizationContext
with a UserProfile
.
The UserProfile
is a normalized view of a User
and it is the addUserToContext
function that maps your User
entity to the UserProfile
.
Here is an example:
export const addUserToContext = async (userId: string) => {
const user = User.fromBackendEntity(
await BaseLoaders.loadOne({ gqlEntityType: User, id: userId })
);
if (!user) {
throw new Error('Bad Request: Unknown user id provided.');
}
return mapUserToProfile(user);
};
From the above you can see we are receiving a userId
and then fetching the user details. Finally, we are mapping the User
entity to the UserProfile
.
This UserProfile
will then be passed to the functions inside the ACL
. For more information on this see the implementing authorization section.
Securing Requests Admin UI
The admin UI is populated using the _graphweaver
query and there may be times where you need to control access to this query.
To do that we can use the Admin-UI’s beforeRead
hook.
Here is an example of using the hook to only allow authenticated users:
export const beforeRead = (context: AuthorizationContext) => {
// Ensure only logged in users can access the admin UI metadata
if (!context.token) {
throw new ForbiddenError('Forbidden');
}
};
Testing Authentication
Finally, to test that the authentication is working open the Admin UI at http://localhost:9000
and you will be redirected to the login page: