I recently started developing an application on adonisjs framework. I had an option to use expressjs but I preferred adonisjs because I love the way it is structured (mostly the laravel style).
I am currently trying to build a RESTFUL API but unable to figure out the basic routing / middleware / apiController (my custom controller to handle all api requests) scenario.
Here's what I've done so far:
Route.post('api/v1/login', 'ApiController.login')
Route.post('api/v1/register', 'ApiController.register')
// API Routes
Route.group('api', function() {
Route.get('users', 'ApiController.getUsers')
}).prefix('/api/v1').middlewares(['auth:api'])
'use strict'
const User = use('App/Model/User')
const Validator = use('Validator')
const FAIL = 0
const SUCCESS = 1
class ApiController {
* login (request, response) {
let jsonResponse = {}
const email = request.input('email')
const password = request.input('password')
// validate form input
const rules = {
email: 'required|email',
password: 'required'
}
const messages = {
'email.required': 'Email field is required.',
'password.required': 'Password field is required.'
}
const validation = yield Validator.validateAll(request.all(), rules, messages)
if (validation.fails()) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = validation.messages()[0].message
} else {
try {
yield request.auth.attempt(email, password)
const user = yield User.findBy('email', email)
const token = yield request.auth.generate(user)
jsonResponse.status = SUCCESS
jsonResponse.response = {}
jsonResponse.response.message = "Logged In Successfully"
jsonResponse.response.user = user
jsonResponse.response.token = token
} catch (e) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = e.message
}
}
return response.json(jsonResponse)
}
}
module.exports = ApiController
'use strict'
const Config = use('Config')
module.exports = {
/*
|--------------------------------------------------------------------------
| Authenticator
|--------------------------------------------------------------------------
|
| Authenticator is a combination of HTTP Authentication scheme and the
| serializer to be used for retrieving users. Below is the default
| authenticator to be used for every request.
|
| Available Schemes - basic, session, jwt, api
| Available Serializers - Lucid, Database
|
*/
authenticator: 'session',
/*
|--------------------------------------------------------------------------
| Session Authenticator
|--------------------------------------------------------------------------
|
| Session authenticator will make use of sessions to maintain the login
| state for a given user.
|
*/
session: {
serializer: 'Lucid',
model: 'App/Model/User',
scheme: 'session',
uid: 'email',
password: 'password'
},
/*
|--------------------------------------------------------------------------
| Basic Auth Authenticator
|--------------------------------------------------------------------------
|
| Basic Authentication works on Http Basic auth header.
|
*/
basic: {
serializer: 'Lucid',
model: 'App/Model/User',
scheme: 'basic',
uid: 'email',
password: 'password'
},
/*
|--------------------------------------------------------------------------
| JWT Authenticator
|--------------------------------------------------------------------------
|
| Jwt authentication works with a payload sent with every request under
| Http Authorization header.
|
*/
jwt: {
serializer: 'Lucid',
model: 'App/Model/User',
scheme: 'jwt',
uid: 'email',
password: 'password',
secret: Config.get('app.appKey')
},
/*
|--------------------------------------------------------------------------
| API Authenticator
|--------------------------------------------------------------------------
|
| Api authenticator authenticates are requests based on Authorization
| header.
|
| Make sure to define relationships on User and Token model as defined
| in documentation
|
*/
api: {
serializer: 'Lucid',
model: 'App/Model/Token',
scheme: 'api'
}
}
'use strict'
module.exports = {
/*
|--------------------------------------------------------------------------
| Content Security Policy
|--------------------------------------------------------------------------
|
| Content security policy filters out the origins not allowed to execute
| and load resources like scripts, styles and fonts. There are wide
| variety of options to choose from.
| @examples
| directives: {
| defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com']
| }
*/
csp: {
directives: {
},
reportOnly: false,
setAllHeaders: false,
disableAndroid: true
},
/*
|--------------------------------------------------------------------------
| X-XSS-Protection
|--------------------------------------------------------------------------
|
| X-XSS Protection saves from applications from XSS attacks. It is adopted
| by IE and later followed by some other browsers.
|
*/
xss: {
enabled: true,
enableOnOldIE: false
},
/*
|--------------------------------------------------------------------------
| Iframe Options
|--------------------------------------------------------------------------
|
| xframe defines whether or not your website can be embedded inside an
| iframe. Choose from one of the following options.
| @available options
| DENY, SAMEORIGIN, ALLOW-FROM http://example.com
*/
xframe: 'DENY',
/*
|--------------------------------------------------------------------------
| No Sniff
|--------------------------------------------------------------------------
|
| Browsers have a habit of sniffing content-type of a response. Which means
| files with .txt extension containing Javascript code will be executed as
| Javascript. You can disable this behavior by setting nosniff to false.
|
*/
nosniff: true,
/*
|--------------------------------------------------------------------------
| No Open
|--------------------------------------------------------------------------
|
| IE users can execute webpages in the context of your website, which is
| a serious security risk. Below options will manage this for you.
|
*/
noopen: true,
/*
|--------------------------------------------------------------------------
| CSRF Protection
|--------------------------------------------------------------------------
|
| CSRF Protection adds another layer of security by making sure, actionable
| routes does have a valid token to execute an action.
|
*/
csrf: {
enable: true,
methods: ['POST', 'PUT', 'DELETE'],
filterUris: ['/api/v1/login', '/api/v1/register'],
compareHostAndOrigin: true
}
}
Now when i hit login web service (using postman). It validates the user but throws an exception at const token = request.auth.generate(user)
and says request.auth.generate is not a function
.
I don't know what is going on. Please help.
Thanks
You need to generate a JWT token (when user calls a login api call) and send it back so that the app which requested the login service can store it and use it to make future requests (Using "Authorization" header with value "Bearer [JWT Token String]"). When app sends another request i.e. get list of business categories (with JWT token in its header), we will validate that request in api middleware. Once the request is validated, we'll serve the request and send back the data in json format.
Here's what your header looks like:
And this is what you actually need to do in your code:
// API Routes
Route.post('/api/v1/register', 'ApiController.register')
Route.post('/api/v1/login', 'ApiController.login')
Route.group('api', function() {
Route.get('/business_categories', 'ApiController.business_categories')
}).prefix('/api/v1').middlewares(['api'])
'use strict'
class Api {
* handle (request, response, next) {
// here goes your middleware logic
const authenticator = request.auth.authenticator('jwt')
const isLoggedIn = yield authenticator.check()
if (!isLoggedIn) {
return response.json({
status: 0,
response: {
message: 'API Authentication Failed.'
}
})
}
// yield next to pass the request to next middleware or controller
yield next
}
}
module.exports = Api
'use strict'
// Dependencies
const Env = use('Env')
const Validator = use('Validator')
const Config = use('Config')
const Database = use('Database')
const Helpers = use('Helpers')
const RandomString = use('randomstring')
const Email = use('emailjs')
const View = use('View')
// Models
const User = use('App/Model/User')
const UserProfile = use('App/Model/UserProfile')
const DesignCenter = use('App/Model/DesignCenter')
const Settings = use('App/Model/Setting')
// Properties
const FAIL = 0
const SUCCESS = 1
const SITE_URL = "http://"+Env.get('HOST')+":"+Env.get('PORT')
// Messages
const MSG_API_AUTH_FAILED = 'Api Authentication Failed.'
const MSG_REGISTERED_SUCCESS = 'Registered Successfully.'
const MSG_LOGGED_IN_SUCCESS = 'Logged In Successfully.'
const MSG_LOGGED_IN_CHECK = 'You Are Logged In.'
const MSG_LOGGED_IN_FAIL = 'Invalid Credentials.'
const MSG_FORGOT_PASS_EMAIL_SUCCESS = 'Your password reset email has been sent. Please check your inbox to continue.'
class ApiController {
* register (request, response) {
let jsonResponse = {}
// validate form input
const validation = yield Validator.validateAll(request.all(), Config.get('validation.api.register.rules'), Config.get('validation.api.register.messages'))
// show error messages upon validation fail
if (validation.fails()) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = validation.messages()[0].message
} else {
// handle card image
let card_image = null
if ( request.file('card_image') ) {
const image = request.file('card_image', {
allowedExtensions: ['jpg', 'png', 'jpeg']
})
if (image.clientSize() > 0) {
const filename = RandomString.generate({length: 30, capitalization: 'lowercase'}) + '.' + image.extension()
yield image.move(Helpers.publicPath(Config.get('constants.user_card_img_upload_path')), filename)
if (!image.moved()) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = image.errors()
return response.json(jsonResponse)
}
// set value for DB
card_image = filename
}
}
// create user
const user = yield User.create({
username: new Date().getTime(),
email: request.input('email'),
password: request.input('password')
})
// create user profile
const user_profile = yield UserProfile.create({
user_id: user.id,
user_type_id: 3, // designer
first_name: request.input('first_name'),
last_name: request.input('last_name'),
business_name: request.input('business_name'),
business_category_id: request.input('business_category'),
card_image: card_image,
phone: request.input('mobile_no'),
is_active: 1
})
jsonResponse.status = SUCCESS
jsonResponse.response = {}
jsonResponse.response.message = MSG_REGISTERED_SUCCESS
jsonResponse.response.user = {
'id': user.id,
'first_name': user_profile.first_name,
'last_name': user_profile.last_name,
'business_name': user_profile.business_name,
'business_category_id': user_profile.business_category_id,
'card_image': user_profile.card_image == null ? "" : SITE_URL + "/" + Config.get('constants.user_card_img_upload_path') + "/" + user_profile.card_image,
'mobile_no': user_profile.phone
}
}
return response.json(jsonResponse)
}
* login (request, response) {
let jsonResponse = {}
const email = request.input('email')
const password = request.input('password')
// validate form input
const validation = yield Validator.validateAll(request.all(), Config.get('validation.api.login.rules'), Config.get('validation.api.login.messages'))
if (validation.fails()) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = validation.messages()[0].message
} else {
try {
const jwt = request.auth.authenticator('jwt')
const token = yield jwt.attempt(email, password)
const user = yield User.findBy('email', email)
const user_profile = yield UserProfile.findBy('user_id', user.id)
// check if user type is designer
if ( user_profile.user_type_id == 3 ) {
jsonResponse.status = SUCCESS
jsonResponse.response = {}
jsonResponse.response.message = MSG_LOGGED_IN_SUCCESS
let card_image = null
if (user_profile.card_image) {
card_image = SITE_URL + "/" + Config.get('constants.user_card_img_upload_path') + "/" + user_profile.card_image
}
jsonResponse.response.user = {
'id': user.id,
'first_name': user_profile.first_name,
'last_name': user_profile.last_name,
'business_name': user_profile.business_name,
'business_category_id': user_profile.business_category_id,
'card_image': card_image,
'mobile_no': user_profile.phone
}
jsonResponse.response.token = token
} else {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = MSG_LOGGED_IN_FAIL
}
} catch (e) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = e.message
}
}
return response.json(jsonResponse)
}
* business_categories (request, response) {
let jsonResponse = {}
try {
jsonResponse.status = SUCCESS
const dbRecords = yield Database.select('id', 'name').from('business_categories')
let records = []
dbRecords.forEach(function(row) {
records.push({
id: row.id,
name: row.name
})
})
jsonResponse.response = records
} catch (e) {
jsonResponse.status = FAIL
jsonResponse.response = {}
jsonResponse.response.message = e.message
}
response.json(jsonResponse)
}
module.exports = ApiController
Since JWT tokens remain valid unless they are expired or deleted (from app, when forcefully logging a user out). We can also set an expiry period as follows:
jwt: {
serializer: 'Lucid',
model: 'App/Model/User',
scheme: 'jwt',
uid: 'email',
password: 'password',
secret: Config.get('app.appKey'),
options: {
// Options to be used while generating token
expiresIn: Ms('3m') // 3 months
}
}
Since most of the api services are not able to send CSRF tokens while sending a POST request, you can exclude those api paths not to be checked for CSRF tokens here in this file as follow:
csrf: {
enable: true,
methods: ['POST', 'PUT', 'DELETE'],
filterUris: [
'/api/v1/login',
'/api/v1/register'
],
compareHostAndOrigin: true
}
Hope this helps :)