-
Notifications
You must be signed in to change notification settings - Fork 911
Description
Hi!
Im quite new to Vue and Nuxt. So far I like the framework until...I got stuck with user authentication. ^^
I made different JWT authentications with React and Next.js in the past and never had bigger problems. Now that I'm using Nuxt with the auth-module I'm kind of frustrated and dont know what to do anymore. The documentation (especially about custom schemes) seems to be incomplete. At least I just couldnt find how to properly refresh tokens.
But Im still optmtistic, that's why I made this post.
For the backend Im using the following:
- Wordpress
- WP GraphQL Plugin
- WPGraphQL JWT Authentication Plugin
Due to GraphQL the frontend cannot use Axios but Apollo instead to make GraphQL Queries/Mutations (right?). After some research I found out how to create a custom scheme and was able to make a login which is redirecting to '/dashboard'. That's cool and I thought my Job was done here. But it wasn't. 5 Minutes (or 300 seconds) after logging in, the token was probably expired and I was redirected back to the login page. For any reason this.check().valid was still true and Idk why.
Anyway this must be the point where I need to refresh the token but I just dont get it working. So Im hoping anyone can help me here. To be honest I dont even know where to trigger the refresh function exactly. In my code I try to refresh the token when user details cannot be loaded which leads to a GraphQL internal server error and I think it's not even the correct way to trigger the token refresh. 🙈 Please forgive me my messy code. I'm still learning.
Thanks in advance if there is anybody out there who can help 💐
This is the code used for the authentication:
nuxt.config.js
modules: [
'@nuxtjs/auth-next',
'@nuxtjs/apollo',
],
auth: {
strategies: {
graphql: {
scheme: '@/schemes/graphqlScheme.js',
token: {
property: false,
maxAge: 300,
},
autoLogout: false,
},
},
redirect: {
login: '/login',
logout: '/login',
home: '/dashboard',
},
}
graphqlScheme.js
import { gql } from 'graphql-tag'
import { RefreshScheme, RefreshController, RefreshToken } from '~auth/runtime'
const LOGIN_MUTATION = gql`
mutation LoginMutation($user: String!, $password: String!) {
login(input: { username: $user, password: $password }) {
authToken
refreshToken
}
}
`
export const USER_DETAILS_QUERY = gql`
query UserDetailsQuery {
viewer {
id
username
jwtAuthToken
jwtAuthExpiration
jwtRefreshToken
}
}
`
const REFRESH_JWT_AUTH_TOKEN = gql`
mutation RefreshJwtAuthToken($refreshToken: String!) {
refreshJwtAuthToken(input: { jwtRefreshToken: $refreshToken }) {
authToken
}
}
`
class CustomRefreshTokenController extends RefreshController {
async handleRefresh() {
const refreshToken = this.scheme.refreshToken.get()
const {
apolloProvider: {
clients: { authConfig: apolloClient },
},
$apolloHelpers,
} = this.$auth.ctx.app
return apolloClient
.mutate({
mutation: REFRESH_JWT_AUTH_TOKEN,
variables: { refreshToken: refreshToken },
fetchPolicy: 'no-cache',
})
.then((res) => {
const refreshedToken = res?.data?.refreshJwtAuthToken?.authToken
this.scheme.token.set(refreshedToken)
// Set your graphql-token
// $apolloHelpers.onLogin(login.authToken)
// Fetch user
// this.$auth.fetchUser()
})
.catch((error) => {
console.log(error)
})
}
}
export default class GraphQLScheme extends RefreshScheme {
constructor(...params) {
super(...params)
// This option will prevent $axios methods from being called
// since we are not using axios
this.options.token.global = false
// Initialize Refresh Token instance
this.refreshToken = new RefreshToken(this, this.$auth.$storage)
// Add token refresh support
this.refreshController = new CustomRefreshTokenController(this)
}
async login(credentials, { reset = true } = {}) {
// this.$auth.logState = 'Logging in'
const {
apolloProvider: {
clients: { authConfig: apolloClient },
},
$apolloHelpers,
// $config,
} = this.$auth.ctx.app
// Ditch any leftover local tokens before attempting to log in
if (reset) {
this.$auth.reset({ resetInterceptor: false })
}
// Make login request
const response = await apolloClient.mutate({
mutation: LOGIN_MUTATION,
variables: credentials,
})
const login = response?.data?.login
login && console.log('Login successful: ', login)
!login && console.log('Login error')
this.$auth.logState = 'Login successful'
this.token.set(login.authToken)
this.$auth.setUserToken(login.authToken, login.refreshToken)
// Set your graphql-token
await $apolloHelpers.onLogin(login.authToken)
// Fetch user
// await this.fetchUser()
// Update tokens
return login.authToken
}
// Override `fetchUser` method of `local` scheme
fetchUser() {
console.log('fetching User details...', this)
// this.$auth.logState = 'Loading user'
const {
apolloProvider: {
clients: { authConfig: apolloClient },
},
} = this.$auth.ctx.app
// Token is required but not available
if (!this.check().valid) {
return
}
// Try to fetch user
return apolloClient
.query({
query: USER_DETAILS_QUERY,
fetchPolicy: 'no-cache', // Important for authentication!
})
.then(({ data }) => {
if (!data.viewer) {
const error = new Error(`User Data response not resolved`)
return Promise.reject(error)
}
this.$auth.setUser(data.viewer)
return data
})
.catch((error) => {
console.log('Error @fetchUser')
this.$auth.refreshTokens()
// this.$auth.callOnError(error, { method: 'fetchUser' })
// return Promise.reject(error)
})
}
async logout() {
const { $apolloHelpers } = this.$auth.ctx.app
$apolloHelpers.onLogout()
return this.$auth.reset({ resetInterceptor: false })
}
initializeRequestInterceptor() {
// Instead of initializing axios interceptors, Do nothing
// Since we are not using axios
}
reset() {
this.$auth.setUser(false)
this.token.reset()
this.refreshToken.reset()
}
}