Skip to content

Instantly share code, notes, and snippets.

@chz
Forked from jengel3/auth.js
Created April 4, 2022 21:45
Show Gist options
  • Save chz/f0a25fd1b12b64daaf86ef871b804fd9 to your computer and use it in GitHub Desktop.
Save chz/f0a25fd1b12b64daaf86ef871b804fd9 to your computer and use it in GitHub Desktop.

Revisions

  1. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions auth.js
    Original file line number Diff line number Diff line change
    @@ -43,7 +43,10 @@ export const mutations = {
    export const actions = {
    async login ({ commit, dispatch }, { email_address, password }) {
    // make an API call to login the user with an email address and password
    const { data: { data: { user, payload } } } = await this.$axios.post('/api/auth/login', { email_address, password })
    const { data: { data: { user, payload } } } = await this.$axios.post(
    '/api/auth/login',
    { email_address, password }
    )

    // commit the user and tokens to the state
    commit(AUTH_MUTATIONS.SET_USER, user)
    @@ -52,7 +55,10 @@ export const actions = {

    async register ({ commit }, { email_addr, password }) {
    // make an API call to register the user
    const { data: { data: { user, payload } } } = await this.$axios.post('/api/auth/register', { email_address, password })
    const { data: { data: { user, payload } } } = await this.$axios.post(
    '/api/auth/register',
    { email_address, password }
    )

    // commit the user and tokens to the state
    commit(AUTH_MUTATIONS.SET_USER, user)
    @@ -64,7 +70,10 @@ export const actions = {
    const { refresh_token } = state

    // make an API call using the refresh token to generate a new access token
    const { data: { data: { payload } } } = await this.$axios.post('/api/auth/refresh', { refresh_token })
    const { data: { data: { payload } } } = await this.$axios.post(
    '/api/auth/refresh',
    { refresh_token }
    )

    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },
  2. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions sample-request.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    const { data: { ... } } = await $axios.get('/api/my-account')
  3. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 21 additions and 0 deletions.
    21 changes: 21 additions & 0 deletions index.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    // store/index.js

    // ....
    export const actions = {
    // https://nuxtjs.org/guide/vuex-store/#the-nuxtserverinit-action
    // automatically refresh the access token on the initial request to the server, if possible
    async nuxtServerInit ({ dispatch, commit, state }) {
    const { access_token, refresh_token } = state.auth

    if (access_token && refresh_token) {
    try {
    // refresh the access token
    await dispatch('auth/refresh')
    } catch (e) {
    // catch any errors and automatically logout the user
    await dispatch('auth/logout')
    }
    }
    },
    }
    // ...
  4. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions auth.js
    Original file line number Diff line number Diff line change
    @@ -25,6 +25,7 @@ export const mutations = {
    [AUTH_MUTATIONS.SET_PAYLOAD] (state, { access_token, refresh_token = null }) {
    state.access_token = access_token

    // refresh token is optional, only set it if present
    if (refresh_token) {
    state.refresh_token = refresh_token
    }
  5. @jengel3 jengel3 renamed this gist Oct 30, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions refresh-payload.json
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    {
    status: "failed",
    text_code: "TOKEN_EXPIRED",
    message: "The JWT token is expired",
    status_code: 401
    "status": "failed",
    "text_code": "TOKEN_EXPIRED",
    "message": "The JWT token is expired",
    "status_code": 401
    }
  7. @jengel3 jengel3 revised this gist Oct 30, 2020. 2 changed files with 18 additions and 3 deletions.
    15 changes: 15 additions & 0 deletions authenticated-axios.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    // plugins/axios.js

    // expose the store, axios client and redirect method from the Nuxt context
    // https://nuxtjs.org/api/context/
    export default function ({ store, app: { $axios }, redirect }) {
    $axios.onRequest((config) => {
    // check if the user is authenticated
    if (store.state.auth.access_token) {
    // set the Authorization header using the access token
    config.headers.Authorization = 'Bearer ' + store.state.auth.access_token
    }

    return config
    })
    }
    6 changes: 3 additions & 3 deletions refresh-payload.json
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,6 @@
    {
    status: 'failed',
    text_code: 'TOKEN_EXPIRED',
    message: 'The JWT token is expired',
    status: "failed",
    text_code: "TOKEN_EXPIRED",
    message: "The JWT token is expired",
    status_code: 401
    }
  8. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions refresh-payload.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,6 @@
    {
    status: 'failed',
    text_code: 'TOKEN_EXPIRED',
    message: 'The JWT token is expired',
    status_code: 401
    }
  9. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 87 additions and 0 deletions.
    87 changes: 87 additions & 0 deletions axios.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,87 @@
    // plugins/axios.js

    // expose the store, axios client and redirect method from the Nuxt context
    // https://nuxtjs.org/api/context/
    export default function ({ store, app: { $axios }, redirect }) {
    const IGNORED_PATHS = ['/auth/login', '/auth/logout', '/auth/refresh']

    $axios.onRequest((config) => {
    // check if the user is authenticated
    if (store.state.auth.access_token) {
    // set the Authorization header using the access token
    config.headers.Authorization = 'Bearer ' + store.state.auth.access_token
    }

    return config
    })

    $axios.onError((error) => {
    return new Promise(async (resolve, reject) => {
    // ignore certain paths (i.e. paths relating to authentication)
    const isIgnored = IGNORED_PATHS.some(path => error.config.url.includes(path))

    // get the status code from the response
    const statusCode = error.response ? error.response.status : -1

    // only handle authentication errors or errors involving the validity of the token
    if ((statusCode === 401 || statusCode === 422) && !isIgnored) {
    // API should return a reason for the error, represented here by the text_code property

    // Example API response:
    // {
    // status: 'failed',
    // text_code: 'TOKEN_EXPIRED',
    // message: 'The JWT token is expired',
    // status_code: 401
    // }

    // retrieve the text_code property from the response, or default to null
    const { data: { text_code } = { text_code: null } } = error.response || {}

    // get the refresh token from the state if it exists
    const refreshToken = store.state.auth.refresh_token

    // determine if the error is a result of an expired access token
    // also ensure that the refresh token is present
    if (text_code === 'TOKEN_EXPIRED' && refreshToken) {

    // see below - consider the refresh process failed if this is a 2nd attempt at the request
    if (error.config.hasOwnProperty('retryAttempts')) {
    // immediately logout if already attempted refresh
    await store.dispatch('auth/logout')

    // redirect the user home
    return redirect('/')
    } else {
    // merge a new retryAttempts property into the original request config to prevent infinite-loop if refresh fails
    const config = { retryAttempts: 1, ...error.config }

    try {
    // attempt to refresh access token using refresh token
    await store.dispatch('auth/refresh')

    // re-run the initial request using the new request config after a successful refresh
    // this response will be returned to the initial calling method
    return resolve($axios(config))
    } catch (e) {
    // catch any error while refreshing the token
    await store.dispatch('auth/logout')

    // redirect the user home
    return redirect('/')
    }
    }
    } else if (text_code === 'TOKEN_INVALID') {
    // catch any other JWT-related error (i.e. malformed token) and logout the user
    await store.dispatch('auth/logout')

    // redirect the user home
    return redirect('/')
    }
    }

    // ignore all other errors, let component or other error handlers handle them
    return reject(error)
    })
    })
    }
  10. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions form-example.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    const email_address = '[email protected]'
    const password = 'abc123'

    await $store.dispatch('auth/login', { email_address, password })
  11. @jengel3 jengel3 revised this gist Oct 30, 2020. 2 changed files with 4 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions auth.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // store/auth.js

    // reusable aliases for mutations
    export const AUTH_MUTATIONS = {
    SET_USER: 'SET_USER',
    2 changes: 2 additions & 0 deletions local-storage.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // plugins/local-storage.js

    import createPersistedState from 'vuex-persistedstate'
    import * as Cookies from 'js-cookie'
    import cookie from 'cookie'
  12. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 25 additions and 0 deletions.
    25 changes: 25 additions & 0 deletions local-storage.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,25 @@
    import createPersistedState from 'vuex-persistedstate'
    import * as Cookies from 'js-cookie'
    import cookie from 'cookie'

    // access the store, http request and environment from the Nuxt context
    // https://nuxtjs.org/api/context/
    export default ({ store, req, isDev }) => {
    createPersistedState({
    key: 'authentication-cookie', // choose any name for your cookie
    paths: [
    // persist the access_token and refresh_token values from the "auth" store module
    'auth.access_token',
    'auth.refresh_token',
    ],
    storage: {
    // if on the browser, parse the cookies using js-cookie otherwise parse from the raw http request
    getItem: key => process.client ? Cookies.getJSON(key) : cookie.parse(req.headers.cookie || '')[key],
    // js-cookie can handle setting both client-side and server-side cookies with one method
    // use isDev to determine if the cookies is accessible via https only (i.e. localhost likely won't be using https)
    setItem: (key, value) => Cookies.set(key, value, { expires: 14, secure: !isDev }),
    // also allow js-cookie to handle removing cookies
    removeItem: key => Cookies.remove(key)
    }
    })(store)
    }
  13. @jengel3 jengel3 revised this gist Oct 30, 2020. 1 changed file with 20 additions and 8 deletions.
    28 changes: 20 additions & 8 deletions auth.js
    Original file line number Diff line number Diff line change
    @@ -1,22 +1,25 @@
    // reusable aliases for mutations
    export const AUTH_MUTATIONS = {
    SET_USER: 'SET_USER',
    SET_PAYLOAD: 'SET_PAYLOAD',
    LOGOUT: 'LOGOUT',
    }

    export const state = () => ({
    access_token: null,
    refresh_token: null,
    id: null,
    email_address: null,
    access_token: null, // JWT access token
    refresh_token: null, // JWT refresh token
    id: null, // user id
    email_address: null, // user email address
    })

    export const mutations = {
    [AUTH_MUTATIONS.SET_USER] (state, { id, email_address, first_name, last_name }) {
    // store the logged in user in the state
    [AUTH_MUTATIONS.SET_USER] (state, { id, email_address }) {
    state.id = id
    state.email_address = email_address
    },

    // store new or updated token fields in the state
    [AUTH_MUTATIONS.SET_PAYLOAD] (state, { access_token, refresh_token = null }) {
    state.access_token = access_token

    @@ -25,6 +28,7 @@ export const mutations = {
    }
    },

    // clear our the state, essentially logging out the user
    [AUTH_MUTATIONS.LOGOUT] (state) {
    state.id = null
    state.email_address = null
    @@ -35,33 +39,41 @@ export const mutations = {

    export const actions = {
    async login ({ commit, dispatch }, { email_address, password }) {
    const { data: { data: { user, payload } } } = await this.$api.users.login(email_address, password)
    // make an API call to login the user with an email address and password
    const { data: { data: { user, payload } } } = await this.$axios.post('/api/auth/login', { email_address, password })

    // commit the user and tokens to the state
    commit(AUTH_MUTATIONS.SET_USER, user)
    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    async register ({ commit }, { email_addr, password }) {
    const { data: { data: { user, payload } } } = await this.$api.users.register(email_address, password)
    // make an API call to register the user
    const { data: { data: { user, payload } } } = await this.$axios.post('/api/auth/register', { email_address, password })

    // commit the user and tokens to the state
    commit(AUTH_MUTATIONS.SET_USER, user)
    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    // given the current refresh token, refresh the user's access token to prevent expiry
    async refresh ({ commit, state }) {
    const { refresh_token } = state

    const { data: { data: { payload } } } = await this.$api.users.refresh(refresh_token)
    // make an API call using the refresh token to generate a new access token
    const { data: { data: { payload } } } = await this.$axios.post('/api/auth/refresh', { refresh_token })

    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    // logout the user
    logout ({ commit, state }) {
    commit(AUTH_MUTATIONS.LOGOUT)
    },
    }

    export const getters = {
    // determine if the user is authenticated based on the presence of the access token
    isAuthenticated: (state) => {
    return state.access_token && state.access_token !== ''
    },
  14. @jengel3 jengel3 created this gist Oct 30, 2020.
    68 changes: 68 additions & 0 deletions auth.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    export const AUTH_MUTATIONS = {
    SET_USER: 'SET_USER',
    SET_PAYLOAD: 'SET_PAYLOAD',
    LOGOUT: 'LOGOUT',
    }

    export const state = () => ({
    access_token: null,
    refresh_token: null,
    id: null,
    email_address: null,
    })

    export const mutations = {
    [AUTH_MUTATIONS.SET_USER] (state, { id, email_address, first_name, last_name }) {
    state.id = id
    state.email_address = email_address
    },

    [AUTH_MUTATIONS.SET_PAYLOAD] (state, { access_token, refresh_token = null }) {
    state.access_token = access_token

    if (refresh_token) {
    state.refresh_token = refresh_token
    }
    },

    [AUTH_MUTATIONS.LOGOUT] (state) {
    state.id = null
    state.email_address = null
    state.access_token = null
    state.refresh_token = null
    },
    }

    export const actions = {
    async login ({ commit, dispatch }, { email_address, password }) {
    const { data: { data: { user, payload } } } = await this.$api.users.login(email_address, password)

    commit(AUTH_MUTATIONS.SET_USER, user)
    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    async register ({ commit }, { email_addr, password }) {
    const { data: { data: { user, payload } } } = await this.$api.users.register(email_address, password)

    commit(AUTH_MUTATIONS.SET_USER, user)
    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    async refresh ({ commit, state }) {
    const { refresh_token } = state

    const { data: { data: { payload } } } = await this.$api.users.refresh(refresh_token)

    commit(AUTH_MUTATIONS.SET_PAYLOAD, payload)
    },

    logout ({ commit, state }) {
    commit(AUTH_MUTATIONS.LOGOUT)
    },
    }

    export const getters = {
    isAuthenticated: (state) => {
    return state.access_token && state.access_token !== ''
    },
    }