Skip to content

Instantly share code, notes, and snippets.

@ctessier
Last active September 30, 2023 13:51
Show Gist options
  • Save ctessier/7fbbe26e135b0cdc71831e4ff8b38221 to your computer and use it in GitHub Desktop.
Save ctessier/7fbbe26e135b0cdc71831e4ff8b38221 to your computer and use it in GitHub Desktop.
Laravel Passport authentication plugin for Vue.js
import store from './store';
/**
* @var {string} OAUTH_TOKEN_ENDPOINT The Laravel Passport endpoint to perform a
* login and refresh token request.
*/
const OAUTH_TOKEN_ENDPOINT = '/oauth/token';
/**
* @var {string} USER_ENDPOINT The Laravel API endpoint to fetch the logged in
* user's information.
*/
const USER_ENDPOINT = '/api/user';
/**
* Passport Auth plugin
*
* Provide `login` and `logout` methods for your components and handles token
* authentication with axios and LaraveL Passport OAuth2 server.
*
* @see https://vuejs.org/v2/guide/plugins.html for more info on Vue.js plugins.
* @see https://github.com/prograhammer/vue-example-project which inspired this
* plugin.
*
* Usage:
*
* // register the plugin...
* Vue.use(passportAuthPlugin, {
* client_id: 1,
* client_secret: 'secret',
* });
*
* // login the user...
* this.$auth.login(username, password)
* .then(() => {
* // user is logged in
* })
* .catch(() => {
* // login failed
* })
*
* // perform authenticated API calls...
*
* // logout the user...
* this.$auth.logout();
*/
export default {
/**
* @private
* @var {string} _client_id The Laravel Passport Password client id.
*/
_client_id: null,
/**
* @private
* @var {string} _client_secret The Laravel Passport Password client secret.
*/
_client_secret: null,
/**
* @private
* @var {string} _scope The Laravel Passport scope.
*/
_scope: '*',
/**
* Initialize the plugin by setting up the interceptors on the `axios`
* instance.
*
* - The user's access token is added on each outgoing request if it exists.
* - If a request fails because of an invalid access token, a refresh token
* request is performed.
*
* @private
* @return {void}
*/
_init () {
axios.interceptors.request.use((request) => {
const { accessToken } = store.state.auth;
if (accessToken && !request.headers.Authorization) {
this._setAuthHeader(request, accessToken);
}
return request;
}, function (error) {
return Promise.reject(error);
});
axios.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (this._isInvalidToken(error.response) && !originalRequest._retry) {
originalRequest._retry = true;
this._refreshToken(store.state.auth.refreshToken, originalRequest);
}
return Promise.reject(error.response.data.error);
});
},
/**
* Set the Authorization header of a given request with an access token.
*
* @private
* @param {Request} request The axios request instance to set the header on.
* @param {string} accessToken The access token to add to the request.
* @return {void}
*/
_setAuthHeader (request, accessToken) {
request.headers.Authorization = `Bearer ${accessToken}`;
},
/**
* Tells whether a response fails because of an invalid token.
*
* @private
* @param {Response} response The axios response instance
* @return {boolean}
*/
_isInvalidToken (response) {
return response.status === 401;
},
/**
* Store the user's access token, refresh token and expires in to the store.
*
* @private
* @param {object} responseData The axios response data.
* @return {void}
*/
_storeToken (responseData) {
const { access_token, refresh_token, expires_in } = responseData;
store.commit('login', { access_token, refresh_token, expires_in });
},
/**
* Store the user's information.
*
* @private
* @param {object} responseData The axios response data.
* @return {void}
*/
_storeUser (responseData) {
const { name, email } = responseData;
store.dispatch('user/receive', { name, email });
},
/**
* Retry a given request.
*
* @private
* @param {Request} request The axios request instance to retry.
* @return {void}
*/
_retry (request) {
this._setAuthHeader(request, store.state.auth.accessToken);
axios(request)
.then((response) => {
return response;
})
.catch((response) => {
return response;
});
},
/**
* Trigger a refresh token process which will also retry a failed request.
*
* @private
* @param {string} refreshToken The user's refresh token.
* @param {Request} request The axios request instance to retry after the
* token has been refreshed.
* @return {Promise}
*/
_refreshToken (refreshToken, request) {
const params = {
'grant_type': 'refresh_token',
'refresh_token': refreshToken,
'client_id': this._client_id,
'client_secret': this._client_secret,
};
return axios.post(OAUTH_TOKEN_ENDPOINT, params)
.then((response) => {
this._storeToken(response.data);
return this._retry(request);
})
.catch((errorResponse) => {
if (this._isInvalidToken(errorResponse)) {
this.logout();
}
return errorResponse;
});
},
/**
* Install the plugin.
*
* @param {Vue} Vue The Vue instance.
* @param {object} options The plugin options.
* @return {void}
*/
install (Vue, options) {
this._client_id = options.client_id;
this._client_secret = options.client_secret;
this._scope = options.scope || this._scope;
this._init();
Vue.prototype.$auth = Vue.auth = this;
},
/**
* Log the user in.
*
* @param {string} username The user's username.
* @param {string} password The user's password.
* @return {Promise}
*/
login (username, password) {
const params = {
'grant_type': 'password',
'client_id': this._client_id,
'client_secret': this._client_secret,
'username': username,
'password': password,
'scope': this._scope,
};
return axios.post(OAUTH_TOKEN_ENDPOINT, params)
.then((response) => {
this._storeToken(response.data);
return axios.get(USER_ENDPOINT);
})
.then((response) => {
this._storeUser(response.data);
})
.catch((error) => {
// TODO: find out why error is undefined when second ajax call
// fails.
throw error || 'unkown_error';
});
},
/**
* Log the user out.
*
* @return {void}
*/
logout () {
return new Promise((resolve, reject) => {
store.commit('logout');
resolve();
});
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment