Vue.js - Basic HTTP Authentication Tutorial & Example
Tutorial built with Vue.js 2.5.17 and Webpack 4.15
Other versions available:
- Vue: Vue 3 + Pinia
- React: React, React + Recoil
- Angular: Angular 14, 10, 9, 8, 6
- Next.js: Next.js 11
- AngularJS: AngularJS
- Blazor: Blazor WebAssembly
The following is a custom example and tutorial on how to setup a simple login page using Vue.js and Basic HTTP Authentication. Webpack 4 is used to compile and bundle all the project files, styling of the example is done with Bootstrap 4.
The tutorial code is available on GitHub at https://github.com/cornflourblue/vue-basic-authentication-example.
Here it is in action: (See on CodeSandbox at https://codesandbox.io/s/lyzjo07r7m)
Running the Vue.js Basic Authentication Tutorial Example Locally
The tutorial example uses Webpack 4 to transpile the ES6 code and bundle the Vue components together, and the webpack dev server is used as the local web server, to learn more about webpack you can check out the webpack docs.
- Install NodeJS and NPM from https://nodejs.org/en/download/.
- Download or clone the tutorial project source code from https://github.com/cornflourblue/vue-basic-authentication-example
- Install all required npm packages by running
npm install
from the command line in the project root folder (where the package.json is located). - Start the application by running
npm start
from the command line in the project root folder.
For more info on setting up a Vue.js development environment see Vue - Setup Development Environment.
Running the Vue.js Basic Auth Example with a Real Backend API
The Vue.js example app uses a fake / mock backend by default so it can run in the browser without a real api, to switch to a real backend api you just have to remove a couple of lines of code from the main vue entry file /src/index.js
below the comment // setup fake backend
.
You can build your own backend api or start with one of the below options:
- To run the Vue.js basic auth example with a real backend API built with Node.js follow the instructions at NodeJS - Basic Authentication Tutorial with Example API
- For a real backend API built with ASP.NET Core 2.1 follow the instructions at ASP.NET Core 2.1 - Basic Authentication Tutorial with Example API
Vue.js Tutorial Project Structure
All source code for the Vue.js basic HTTP authentication app is located in the /src folder. Inside the src folder there is a folder per feature (app, home, login) and a couple of folders for non-feature code that can be shared across different parts of the app (_services, _helpers).
I prefixed non-feature folders with an underscore "_" to group them together and make it easy to distinguish between features and non-features, it also keeps the project folder structure shallow so it's quick to see everything at a glance from the top level and to navigate around the project.
Click any of the below links to jump down to a description of each file in the tutorial along with it's code:
Vue.js Tutorial Helpers Folder
The helpers folder contains all the bits and pieces that don't fit into other folders but don't justify having a folder of their own.
Vue.js Tutorial Auth Header
Auth header is a helper function that returns an HTTP Authorization header containing the basic authentication credentials (base64 username and password) of the currently logged in user from local storage. If the user isn't logged in an empty object is returned.
The auth header is used to make authenticated HTTP requests to the server api using basic HTTP authentication.
export function authHeader() {
// return authorization header with basic auth credentials
let user = JSON.parse(localStorage.getItem('user'));
if (user && user.authdata) {
return { 'Authorization': 'Basic ' + user.authdata };
} else {
return {};
}
}
Vue.js Tutorial Fake / Mock Backend
The fake backend is used for running the tutorial example without a server api (backend-less). It monkey patches the fetch()
function to intercept certain api requests and mimic the behaviour of a real api. Any requests that aren't intercepted get passed through to the real fetch()
function that's built into the browser.
I created it so I could focus the tutorial on the Vue.js code and not worry about the backend, and also to make it work on CodeSandbox.
export function configureFakeBackend() {
let users = [{ id: 1, username: 'test', password: 'test', firstName: 'Test', lastName: 'User' }];
let realFetch = window.fetch;
window.fetch = function (url, opts) {
return new Promise((resolve, reject) => {
// wrap in timeout to simulate server api call
setTimeout(() => {
// authenticate
if (url.endsWith('/users/authenticate') && opts.method === 'POST') {
// get parameters from post request
let params = JSON.parse(opts.body);
// find if any user matches login credentials
let filteredUsers = users.filter(user => {
return user.username === params.username && user.password === params.password;
});
if (filteredUsers.length) {
// if login details are valid return user details
let user = filteredUsers[0];
let responseJson = {
id: user.id,
username: user.username,
firstName: user.firstName,
lastName: user.lastName
};
resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(responseJson)) });
} else {
// else return error
reject('Username or password is incorrect');
}
return;
}
// get users
if (url.endsWith('/users') && opts.method === 'GET') {
// check for fake auth token in header and return users if valid, this security
// is implemented server side in a real application
if (opts.headers && opts.headers.Authorization === `Basic ${window.btoa('test:test')}`) {
resolve({ ok: true, text: () => Promise.resolve(JSON.stringify(users)) });
} else {
// return 401 not authorised if token is null or invalid
reject('Unauthorised');
}
return;
}
// pass through any requests not handled above
realFetch(url, opts).then(response => resolve(response));
}, 500);
});
}
}
Vue.js Tutorial Router
The vue router defines all of the routes for the application, and contains a function that runs before each route change to prevent unauthenticated users from accessing restricted routes.
import Vue from 'vue';
import Router from 'vue-router';
import HomePage from '../home/HomePage'
import LoginPage from '../login/LoginPage'
Vue.use(Router);
export const router = new Router({
mode: 'history',
routes: [
{ path: '/', component: HomePage },
{ path: '/login', component: LoginPage },
// otherwise redirect to home
{ path: '*', redirect: '/' }
]
});
router.beforeEach((to, from, next) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const loggedIn = localStorage.getItem('user');
if (authRequired && !loggedIn) {
return next({
path: '/login',
query: { returnUrl: to.path }
});
}
next();
})
Vue.js Tutorial Helpers Index
The helpers index file groups all helper exports together so they can be imported in other parts of the app using only the folder path, and enables importing multiple helpers in a single statement (e.g. import { helper1, helper2, ... } from '../_helpers'
).
export * from './fake-backend';
export * from './router';
export * from './auth-header';
Vue.js Tutorial Services Folder
The services layer handles all http communication with backend apis for the application, each service encapsulates the api calls for a content type (e.g. users) and exposes methods for performing various operations (e.g. CRUD operations). Services can also have methods that don't wrap http calls, for example the userService.logout()
method just removes the user object from local storage.
I like wrapping http calls and implementation details in a services layer, it provides a clean separation of concerns and simplifies the vue components that use the services.
Vue.js Tutorial User Service
The user service encapsulates all backend api calls for performing CRUD operations on user data, as well as logging and out of the example application. The service methods are exported via the userService
object at the top of the file, and the implementation of each method is located in the functions below.
In the handleResponse method the service checks if the http response from the api is 401 Unauthorized and automatically logs the user out. This handles if the basic authentication credentials are invalid.
import config from 'config';
import { authHeader } from '../_helpers';
export const userService = {
login,
logout,
getAll
};
function login(username, password) {
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
};
return fetch(`${config.apiUrl}/users/authenticate`, requestOptions)
.then(handleResponse)
.then(user => {
// login successful if there's a user in the response
if (user) {
// store user details and basic auth credentials in local storage
// to keep user logged in between page refreshes
user.authdata = window.btoa(username + ':' + password);
localStorage.setItem('user', JSON.stringify(user));
}
return user;
});
}
function logout() {
// remove user from local storage to log user out
localStorage.removeItem('user');
}
function getAll() {
const requestOptions = {
method: 'GET',
headers: authHeader()
};
return fetch(`${config.apiUrl}/users`, requestOptions).then(handleResponse);
}
function handleResponse(response) {
return response.text().then(text => {
const data = text && JSON.parse(text);
if (!response.ok) {
if (response.status === 401) {
// auto logout if 401 response returned from api
logout();
location.reload(true);
}
const error = (data && data.message) || response.statusText;
return Promise.reject(error);
}
return data;
});
}
Vue.js Tutorial Services Index
The services index file groups all service exports together so they can be imported in other parts of the app using only the folder path, and enables importing multiple services in a single statement (e.g. import { service1, service2, ... } from '../_services'
).
export * from './user.service';
Vue.js Tutorial App Feature Folder
The app folder is for vue components and other code that is used only by the app component in the tutorial application.
Vue.js Tutorial App Component
The app component is the root component for the vue.js tutorial application, it contains the outer html and router-view for the tutorial app.
export * from './user.service';
<template>
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-sm-6 offset-sm-3">
<router-view></router-view>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app'
};
</script>
Vue.js Tutorial Home Feature Folder
The home folder is for vue components and other code that is used only by the home page component in the tutorial application.
Vue.js Tutorial Home Page Component
The home page component is displayed after signing in to the application, it shows the signed in user's name plus a list of all users in the tutorial app. The users are fetched from the api by calling userService.getAll()
inside the created()
vue component lifecycle hook.
<template>
<div>
<h1>Hi {{user.firstName}}!</h1>
<p>You're logged in with Vue.js & Basic HTTP Authentication!!</p>
<h3>Users from secure api end point:</h3>
<em v-if="users.loading">Loading users...</em>
<ul v-if="users.length">
<li v-for="user in users" :key="user.id">
{{user.firstName + ' ' + user.lastName}}
</li>
</ul>
<p>
<router-link to="/login">Logout</router-link>
</p>
</div>
</template>
<script>
import { userService } from '../_services';
export default {
data () {
return {
user: {},
users: []
}
},
created () {
this.user = JSON.parse(localStorage.getItem('user'));
this.users.loading = true;
userService.getAll().then(users => this.users = users);
}
};
</script>
Vue.js Tutorial Login Feature Folder
The login folder is for vue components and other code that is used only by the login page component in the tutorial application.
Vue.js Tutorial Login Page Component
The login page component renders a login form with username and password fields. It displays validation messages for invalid fields when the user attempts to submit the form. If the form is valid the userService.login(username, password)
method is called.
In the created()
function the userService.logout();
method is called which logs the user out if they're logged in, this enables the login page to also be used as the logout page.
<template>
<div>
<div class="alert alert-info">
Username: test<br />
Password: test
</div>
<h2>Login</h2>
<form @submit.prevent="handleSubmit">
<div class="form-group">
<label for="username">Username</label>
<input type="text" v-model="username" name="username" class="form-control" :class="{ 'is-invalid': submitted && !username }" />
<div v-show="submitted && !username" class="invalid-feedback">Username is required</div>
</div>
<div class="form-group">
<label htmlFor="password">Password</label>
<input type="password" v-model="password" name="password" class="form-control" :class="{ 'is-invalid': submitted && !password }" />
<div v-show="submitted && !password" class="invalid-feedback">Password is required</div>
</div>
<div class="form-group">
<button class="btn btn-primary" :disabled="loading">Login</button>
<img v-show="loading" src="data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQJCgAAACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkECQoAAAAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkECQoAAAAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkECQoAAAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQJCgAAACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQJCgAAACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAkKAAAALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==" />
</div>
<div v-if="error" class="alert alert-danger">{{error}}</div>
</form>
</div>
</template>
<script>
import { router } from '../_helpers';
import { userService } from '../_services';
export default {
data () {
return {
username: '',
password: '',
submitted: false,
loading: false,
returnUrl: '',
error: ''
}
},
created () {
// reset login status
userService.logout();
// get return url from route parameters or default to '/'
this.returnUrl = this.$route.query.returnUrl || '/';
},
methods: {
handleSubmit (e) {
this.submitted = true;
const { username, password } = this;
// stop here if form is invalid
if (!(username && password)) {
return;
}
this.loading = true;
userService.login(username, password)
.then(
user => router.push(this.returnUrl),
error => {
this.error = error;
this.loading = false;
}
);
}
}
};
</script>
Vue.js Tutorial Main Index HTML
The main index html file contains the outer html for the whole tutorial application. When the app is started with npm start
, Webpack bundles up all of the vue.js code into a single javascript file and injects it into the body of the page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue.js - Basic HTTP Authentication Example & Tutorial</title>
<link href="//netdna.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" />
<style>
a { cursor: pointer; }
</style>
</head>
<body>
<div id="app"></div>
</body>
</html>
Vue.js Tutorial App Entrypoint
The root index.js file bootstraps the vue.js tutorial application by rendering the App
component into the #app
div element defined in the main index html file above.
The tutorial app uses a fake / mock backend that stores data in browser local storage, to switch to a real backend api simply remove the fake backend code below the comment // setup fake backend
.
import Vue from 'vue';
import { router } from './_helpers';
import App from './app/App';
// setup fake backend
import { configureFakeBackend } from './_helpers';
configureFakeBackend();
new Vue({
el: '#app',
router,
render: h => h(App)
});
Vue.js Tutorial Babel RC (Run Commands)
The babel config file defines the presets used by babel to transpile the ES6 code. The babel transpiler is run by webpack via the babel-loader
module configured in the webpack.config.js file below.
{
"presets": [
"env",
"stage-0"
]
}
Vue.js Tutorial Package.json
The package.json file contains project configuration information including package dependencies which get installed when you run npm install
. Full documentation is available on the npm docs website.
{
"name": "vue-basic-authentication-example",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/cornflourblue/vue-basic-authentication-example.git"
},
"license": "MIT",
"scripts": {
"start": "webpack-dev-server --open"
},
"dependencies": {
"vue": "^2.5.16",
"vue-router": "^3.0.1"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-0": "^6.24.1",
"babel-preset-vue": "^2.0.2",
"css-loader": "^0.28.11",
"html-webpack-plugin": "^3.2.0",
"path": "^0.12.7",
"vue-loader": "^14.2.3",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.15.0",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.3"
}
}
Vue.js Tutorial Webpack Config
Webpack is used to compile and bundle all the project files so they're ready to be loaded into a browser, it does this with the help of loaders and plugins that are configured in the webpack.config.js file. For more info about webpack check out the webpack docs.
The webpack config file also defines a global config object for the application using the externals
property, you can also use this to define different config variables for your development and production environments.
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
resolve: {
extensions: ['.js', '.vue']
},
module: {
rules: [
{
test: /\.vue?$/,
exclude: /(node_modules)/,
use: 'vue-loader'
},
{
test: /\.js?$/,
exclude: /(node_modules)/,
use: 'babel-loader'
}
]
},
plugins: [new HtmlWebpackPlugin({
template: './src/index.html'
})],
devServer: {
historyApiFallback: true
},
externals: {
// global app config object
config: JSON.stringify({
apiUrl: 'http://localhost:4000'
})
}
}
Need Some Vue Help?
Search fiverr for freelance Vue developers.
Follow me for updates
When I'm not coding...
Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!