Categories
Nuxt.js

Adding Authentication to a Nuxt App with the Nuxt Auth Module

With the Nuxt Auth module, we can add authentication to our Nuxt app with ease.

In this article, we’ll look at how to add authentication to our Nuxt app with Express and the Nuxt Auth module.

Getting Started

We’ve to add the Axios and Nuxt Auth modules.

Nuxt Auth uses Axios for sending requests.

To install it, we run:

yarn add @nuxtjs/auth @nuxtjs/axios

or:

npm install @nuxtjs/auth @nuxtjs/axios

Then in nuxt.config.js , we add:

const baseURL = "https://ReasonableRecursiveSupercollider--five-nine.repl.co";

export default {
  mode: 'universal',
  target: 'server',
  head: {
    title: process.env.npm_package_name || '',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  css: [
  ],
  plugins: [
  ],
  components: true,
  buildModules: [
  ],
  modules: [
    '@nuxtjs/axios',
    '@nuxtjs/auth'
  ],
  axios: {
    baseURL
  },
  build: {
  }
}

We add the baseURL to the axios property to set the base URL of our requests.

Also, we add the '@nuxtjs/auth' module to the modules array to add the module.

In the store folder, we’ve to add index.js to use the Nuxt auth module.

Back End

We use Express as the back end for our app.

To create it, we install Express with some packages by running:

npm i express body-parser cors

in a project folder.

Then we create index.js in the same folder and add:

const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors')
const app = express();
app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/api/auth/login', (req, res) => {
  res.json({});
});

app.post('/api/auth/logout', (req, res) => {
  res.json({});
});

app.get('/api/auth/user', (req, res) => {
  res.json({});
});

app.listen(3000, () => console.log('server started'));

We need the cors middleware to accept cross-domain requests.

The routes are the endpoints we send our auth requests to.

We should add logic to check for users in real-world applications.

Front End

In our Nuxt app, we add a login.vue component and add:

<template>
  <div class="container">
    <form @submit.prevent="userLogin">
      <div>
        <label>Username</label>
        <input type="text" v-model="login.username" />
      </div>
      <div>
        <label>Password</label>
        <input type="text" v-model="login.password" />
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  middleware: "auth",
  data() {
    return {
      login: {},
    };
  },
  auth: {
    strategies: {
      local: {
        endpoints: {
          login: {
            url: `/api/auth/login`,
            method: "post",
            propertyName: "token",
          },
          logout: { url: `/api/auth/logout`, method: "post" },
          user: {
            url: `/api/auth/user`,
            method: "get",
            propertyName: "user",
          },
        },
      },
    },
  },
  methods: {
    async userLogin() {
      try {
        let response = await this.$auth.loginWith("local", {
          data: this.login,
        });
        console.log(response);
      } catch (err) {
        console.log(err);
      }
    },
  },
};
</script>

It has a login form that takes the username and password.

In the component options, we enable the auth middleware with the middleware: 'auth' property.

Then we set the auth options by adding the auth.strategies.local property to add the endpoints, we’ll make requests to.

login is used by the this.$auth.loginWith method to make requests.

auth.strategies.local optionally takes the tokenRequired and tokenType properties.

tokenRequired: false disables all token handling.

tokenType is the authorization header name to be used in Axios requests.

this.$auth.loginWith takes the strategy as the first argument and the data as the 2nd argument with the data property.

So when we submit the form, the userLogin method is called and the request to https://ReasonableRecursiveSupercollider--five-nine.repl.co/api/auth/login is made with the username and password in the request payload.

Conclusion

We can add a login form with the Nuxt and use the auth module to make the requests.

Categories
JavaScript

Custom Validation with Joi — Methods

Joi is a library that lets us validate an object’s structure with ease.

In this article, we’ll look at how to validate objects with Joi.

isError

We can check if a property is an Error instance with the isError method:

Joi.isError(new Error());

isExpression

The isExpressiuon method lets us check if the argument is an expression:

const expression = Joi.x('{a}');
Joi.isExpression(expression);

isRef

isRef lets us check if the argument is a reference to another schema:

const ref = Joi.ref('a');
Joi.isRef(ref);

isSchema

isSchema lets us check if the argument is a schema:

const schema = Joi.any();
Joi.isSchema(schema);

ref

ref generates a reference to another schema.

For example, we can write:

const Joi = require("joi");
const schema = Joi.object({
  a: Joi.ref("b.c"),
  b: {
    c: Joi.any()
  }
});

to apply the Joi.any schema by referencing the schema of the b.c property.

The refercnes are relative, so we can write something like:

const Joi = require("joi");
const schema = Joi.object({
  x: {
    b: {
      c: Joi.any(),
      d: Joi.ref("c")
    }
  }
});

to reference the c property within b .

types

We can use the types method to get validators that we can use to create schemas.

For example, we can write:

const Joi = require("joi");
const { object, string } = Joi.types();

const schema = object.keys({
  property: string.min(10)
});

to get the string schema from the types method.

any

any creates a schema that matches any data type:

const any = Joi.any();
await any.validateAsync('a');

type

We can use the type property to get the type of the schema:

const schema = Joi.string();
schema.type === 'string';

allow

The allow method lets us set the values we allow in a schema:

const schema = {
  a: Joi.any().allow("a")
};

only 'a' is allows as the value of property a .

We can check for multiple values by passing in more arguments.

artifact

artifact returns the validation result:

const schema = {
  a: [
    Joi.number().max(10).artifact("under"),
    Joi.number().min(11).artifact("over")
  ]
};

custom

We can create our own schema with the custom method:

const Joi = require("joi");
const method = (value, helpers) => {
  if (value === "1") {
    throw new Error("nope");
  }

  if (value === "2") {
    return "3";
  }
  return value;
};

const schema = Joi.string().custom(method, "custom validation");

We check the value and return the value we want.

We can also throw errors if the value isn’t valid.

Also, we can throw existing errors:

const Joi = require("joi");
const method = (value, helpers) => {
  if (value === "1") {
    return helpers.error("any.invalid");
  }

  if (value === "2") {
    return "3";
  }
  return value;
};

const schema = Joi.string().custom(method, "custom validation");

We get the error from the helpers object.

empty

empty matches an empty schema:

let schema = Joi.string().empty('');

This will consider an empty string to be valid.

And if we have:

let schema = Joi.string().empty();

then we match undefined .

error

error lets us throw an error if validation fails:

const schema = Joi.string().error(new Error('string required'));

We throw the error we pass into the error method if validation fails.

extract

extract extracts a schema from another schema:

const schema = Joi.object({ foo: Joi.object({ bar: Joi.number() }) });
const number = schema.extract('foo.bar');

This will take the schema from foo.bar .

Conclusion

We can validate values with various Joi methods.

Categories
JavaScript

Custom Validation with Joi

Joi is a library that lets us validate an object’s structure with ease.

In this article, we’ll look at how to validate objects with Joi.

Getting Started

We can install Joi by running:

npm i joi

Then we can use it by writing:

const Joi = require("joi");

const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]+$"))
});

const value = schema.validate({ username: "abc", password: "abc123" });
console.log(value);

We create a schema with Joi by calling its built-in methods.

The keys are the property names,

string makes sure it’s a string.

alphanum checks for alphanumeric characters.

min checks for a minimum amount of characters.

max checks for a max amount of characters.

required makes the property value required.

pattern checks for a given regex pattern.

Once we created a schema, we called schema.validate to validate the property values.

It returns an object with the value and error properties.

value has the argument we passed in.

error has any error that are found.

If we call:

const value = schema.validate({});

then error would be an object with the message:

'ValidationError: "username" is required'

The details property has more details.

We can do the validation asynchronously with the validateAsync method.

For example, we can write:

const Joi = require("joi");

const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp("^[a-zA-Z0-9]+$"))
});

(async () => {
  try {
    const value = await schema.validateAsync({
      username: "abc",
      password: "abc123"
    });
    console.log(value);
  } catch (err) {
    console.log(err);
  }
})();

to do the validation.

value has the value we passed into validateAsync .

And err has validation errors if there are any.

Assertions

The assert mthod lets us validate against a schema and throws an error if validation fails.

For example, we can write:

Joi.assert("x", Joi.number());

Then we would get:

"value" must be a number

error displayed.

The first argument is the value to validate.

The 2nd argument is the data type to validate against.

The attempt method validates against a schema and returns a valid object.

It’ll throw an error if validation fails.

For example, we can write:

const result = Joi.attempt("10", Joi.number());
console.log(result);

and result is 10.

Defaults

The defaults method returns a new joi instance that applies the provided modifier function to every new schema.

For example, if we have:

const Joi = require("joi");
const custom = Joi.defaults((schema) => {
  switch (schema.type) {
    case "string":
      return schema.allow("");
    case "object":
      return schema.min(1);
    default:
      return schema;
  }
});

const schema = custom.object();

Then schema is schema.min(1) since 'object' is the value of schema.type .

in

The in method returns a reference to another schema.

For example, we can write:

const Joi = require("joi");
const schema = Joi.object({
  a: Joi.array().items(Joi.number()),
  b: Joi.number().valid(Joi.in("a"))
});

Then we check against the schema for property a , which is an array that have numbers.

So we check that it’s a number that’s in the a array with the in method.

Conclusion

We can check for object properties and values with Joi.

Categories
JavaScript

Custom Validation with Joi — Annotations and Failover Values

Joi is a library that lets us validate an object’s structure with ease.

In this article, we’ll look at how to validate objects with Joi.

any

any creates a schema that matches any data type:

const any = Joi.any();
await any.validateAsync('a');

failover

We can set a failover value if schema validation fails with failover .

forbidden

forbidden lets us mark a key as forbidden:

const schema = {
  a: Joi.any().forbidden()
};

Now validation fails if a is in our object.

id

The id method lets us set the schema ID for getting the schema.

invalid

invalid lets us mark invalid values:

const schema = {
  a: Joi.any().invalid("a"),
  b: Joi.any().invalid("b", "B")
};

label

label lets us override the key name in error messages:

const schema = {
  name: Joi.string().label("Name")
};

meta

meta lets us add metadata to a key:

const schema = Joi.any().meta({ index: true });

note

note adds notes to as individual arguments to annotate it:

const schema = Joi.any().note('this is special', 'this is important');

optional

optional marks a key as optional:

const schema = Joi.any().optional();

prefs

prefs lets us sets the validation options.

raw

raw outputs the original untouched value instead of the casted value.

So if we have:

const rawTimestampSchema = Joi.date().timestamp().raw();
rawTimestampSchema.validate('12376834097810');

We validate the string instead of casting it to a Date object.

required

required make a key as required:

const schema = Joi.any().required();

ruleset

ruleset lets us apply multiple rule options:

const schema = Joi.number().ruleset.min(1).max(20).rule({ message: 'Number must be between 1 and 20' });

shared

shared registers a schema to be used by descendants of the schema in linked references.

If we have:

const schema = Joi.object({
  a: [Joi.string(), Joi.link("#x")],
  b: Joi.link("#type.a")
})
  .shared(Joi.number().id("x"))
  .id("type");

Then b and x is validated with the rules from a

strip

strip marks a key to be removed from an object or array after validation to sanitize output:

const schema = Joi.object({
  username: Joi.string(),
  password: Joi.string().strip()
});

password will be removed from the output if we validate against this schema.

tag

The tag method annotated the key with tags:

const schema = Joi.any().tag('foo', 'bar');

tailor

tailor returns a schema which is created by alter :

const schema = Joi.object({
  key: Joi.string().alter({
    get: (schema) => schema.required(),
    post: (schema) => schema.forbidden()
  })
});

const getSchema = schema.tailor("get");

getSchema would be the value of the get property.

unit

unit annotates the unit:

const schema = Joi.number().unit('milliseconds');

valid

valid marks the valid values:

const schema = {
  a: Joi.any().valid("a")
};

'a' is the valid value of a .

validate

validate validates data with a schema:

const schema = Joi.object({
  a: Joi.number()
});

const value = {
  a: "123"
};

const result = schema.validate(value);

result would be true because value.a is a number.

validateAsync

validateAsync does the validation asynchronously.

It returns a promise which resolves to the validation result.

warning

warning sets the warning:

const schema = Joi.any()
    .warning('custom.x', { w: 'world' })
    .message({ 'custom.x': 'hello {#w}!' });

const { value, error, warning } = schema.validate('anything');

#w has the value of w in the object we pass in.

Conclusion

We can customize our validation with warnings, references, and other annotations.

Categories
Buefy

Buefy — Time Picker and File Input

Buefy is a UI framework that’s based on Bulma.

In this article, we’ll look at how to use Buefy in our Vue app.

Time Picker

Buefy has a time picker component.

We can add the b-timepicker component to use it.

For example, we can write;

<template>
  <section>
    <b-field>
      <b-timepicker
        rounded
        placeholder="Click to select..."
        enable-seconds
        hour-format="12"
        locale="en"
      ></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {};
  }
};
</script>

The component takes a few props.

rounded makes the input box have round corners.

placeholder has the placeholder.

enable-seconds has a dropdown to pick the seconds.

hour-format sets the hour format.

locale sets the locale.

We can make the time picker editable with the editable prop:

<template>
  <section>
    <b-field>
      <b-timepicker
        rounded
        placeholder="Click to select..."
        enable-seconds
        hour-format="12"
        locale="en"
        editable
      ></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {};
  }
};
</script>

The range of allowed values can be set with the min-time and max-time props:

<template>
  <section>
    <b-field>
      <b-timepicker placeholder="Click to select..." :min-time="minTime" :max-time="maxTime"></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    const min = new Date();
    min.setHours(0);
    min.setMinutes(0);
    const max = new Date();
    max.setHours(2);
    max.setMinutes(0);
    return {
      minTime: min,
      maxTime: max
    };
  }
};
</script>

The footer can be changed by populating the default slot:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" placeholder="Click to select...">
        <button class="button is-primary" @click="time = new Date()">
          <span>Now</span>
        </button>

        <button class="button is-danger" @click="time = null">
          <span>Clear</span>
        </button>
      </b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

We added 2 buttons into the default slot and they’ll be displayed below the time picker.

Also, we can make it inline with the inline prop:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" inline></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

The increments of the dropdown can be set with the incrementMinutes and incrementHours props:

<template>
  <section>
    <b-field>
      <b-timepicker v-model="time" :incrementMinutes="5" :incrementHours="2"></b-timepicker>
    </b-field>
  </section>
</template>

<script>
export default {
  data() {
    return {
      time: new Date()
    };
  }
};
</script>

File Upload Input

The b-upload component is the file upload input.

For example, we can write:

<template>
  <b-field class="file is-primary" :class="{'has-name': !!file}">
    <b-upload v-model="file">
      <span class="file-cta">
        <span>Click to upload</span>
      </span>
      <span class="file-name" v-if="file">{{ file.name }}</span>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

to add it.

The file-cta class makes the input display as a button.

v-model binds the selected file to the file state.

We can make the file input appears as a drop zone with the drag-drop prop:

<template>
  <b-field class="file is-primary" :class="{'has-name': !!file}">
    <b-upload v-model="file" drag-drop>
      <section class="section">
        <div class="content has-text-centered">
          <p>Drop your files here or click to upload</p>
        </div>
      </section>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

The expanded prop makes the button longer:

<template>
  <b-field class="file">
    <b-upload v-model="file" expanded>
      <a class="button is-primary is-fullwidth">
        <span>{{ file && file.name || "Click to upload"}}</span>
      </a>
    </b-upload>
  </b-field>
</template>

<script>
export default {
  data() {
    return {
      file: null
    };
  }
};
</script>

Conclusion

We can add a time picker and file upload input with Buefy.