Open daldridge-cs opened 8 years ago
Hi! I'm using RAML and Osprey for one university project of mine. We decided to implement JWT-based authentication so I've been experimenting a bit during the last few days and I've faced the same problem. This is an example project I've produced, I hope it's useful for anyone wanting to use JWTs within Osprey.
This is ./assets/api.raml
containing some simple API and custom security schema:
#%RAML 0.8
title: StudyRooms
version: 0.0.1
baseUri: &domain localhost
protocols:
- HTTP
mediaType: application/json
securitySchemes:
- JWT:
description: Jason Web Schemas
type: x-jwt
describedBy:
headers:
Authorization:
required: true
type: string
responses:
401:
description: |
Bad or expired token. This can happen if the user or Dropbox
revoked or expired an access token. To fix, you should re-
authenticate the user.
settings:
x-jwt-header:
x-jwt-typ: JWT
x-jwt-alg: HS512
x-jwt-payload:
x-jwt-iss: *domain
x-jwt-aud: *domain
/index:
get:
responses:
200:
body:
application/json:
schema: | #!include schemas/public.json
{
"type" : "array",
"items" : {
"type" : "object",
"properties": {
"public": {
"type": "string",
"required": true
}
}
}
}
/users:
get:
securedBy:
- JWT
displayName: Show all users
responses:
200:
body:
text/plain:
example: Auth
post:
displayName: Create new user
body:
application/json:
schema: |
{
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": ["username", "password"]
}
responses:
204:
description: User successfully registered
409:
description: Username already in use
400:
description: Request is not well-formed
/{username}:
uriParameters:
username:
displayName: User Name
type: string
/auth:
post:
displayName: Authenticate username
body:
application/json:
schema: |
{
"type": "object",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
}
},
"required": ["username", "password"]
}
responses:
200:
body:
application/json:
schema: |
{
"type": "object",
"properties": {
"token": {
"required": true,
"type": "string"
}
}
}
401:
description: Wrong Password
409:
description: Error
400:
description: Request is not well-formed
And this is ./app.js
:
var express = require('express');
var parser = require('raml-parser');
var osprey = require('osprey');
var path = require('path');
var jsonwebtoken = require('jsonwebtoken');
var passport = require('passport');
var jwt = require('passport-jwt');
var JwtStrategy = jwt.Strategy;
var ExtractJwt = jwt.ExtractJwt;
var usersStorage = require('./storage/users');
var secret = 'sarebbe ottimale se questo segreto fosse un pelo più random';
var app = express();
var apiPath = path.join(__dirname, 'assets', 'api.raml');
var signOpts = {
expiresIn: 15 * 60
};
function jwtSecurity(scheme, name) {
var authOpts = {
jwtFromRequest: ExtractJwt.fromAuthHeader(), // TODO whatch at scheme for extractor
secretOrKey: secret,
issuer: scheme.settings['x-jwt-payload']['x-jwt-iss'],
audience: scheme.settings['x-jwt-payload']['x-jwt-aud'],
algorithms: [
scheme.settings['x-jwt-header']['x-jwt-alg']
]
};
signOpts.audience = authOpts.audience;
signOpts.issuer = authOpts.issuer;
signOpts.algorithm = authOpts.algorithms[0];
passport.use(
new JwtStrategy(authOpts,
function verify(payload, done) {
usersStorage.getUser(payload.sub, function (err, user) {
if (user) {
done(false, user);
} else {
done(false, false);
}
});
}
)
);
return {
handler: function (options, path) {
return passport.authenticate('jwt', {session: false});
}
}
}
app.use(passport.initialize());
parser.loadFile(apiPath).then(function (raml) {
app.use(osprey.server(raml));
app.use(osprey.security(raml, {
JWT: jwtSecurity
}));
var router = osprey.Router();
router.get('/index', function (res, res, next) {
res.json({
public: 'Welcome! This is an example!'
});
});
router.get('/users', function (req, res, next) {
usersStorage.getAll(function (err, us) {
res.json(us);
});
});
router.post('/users', function (req, res, next) {
usersStorage.addUser(req.body.username, req.body.password, function (err, user) {
if (err) {
res.statusCode = 409;
res.end();
} else {
res.statusCode = 204;
res.end();
}
});
});
router.post('/users/{username}/auth', function (req, res, next) {
var password = req.body.password;
var username = req.params.username;
username = username.substr(1, username.length - 2);
if (password) {
usersStorage.getUser(username, function (err, u) {
if (err) {
res.statusCode = 409;
res.end();
} else {
if (u.password === password) {
var token = jsonwebtoken.sign({sub: username}, secret, signOpts);
res.json({token: token});
} else {
res.statusCode = 401;
res.end();
}
}
});
} else {
res.statusCode = 400;
res.end();
}
});
app.use(router);
app.use(osprey.errorHandler());
app.use(function (err, req, res, next) {
res.statusCode = err.statusCode;
res.json({
message: err.message,
status: err.statusCode
});
});
app.listen(3000)
});
As you can see passport-jwt is gracefully integrated. You can also specify some parameters within RAML specification. Sadly this implementation only allow tokens to be stored into the Authorization
header: but it's quite simple to extend this behaviour.
I really hope this could be useful to someone. I know it's poorly commented, if something is unclear just ask!
ciao @gciatto , tks for the kick start with jwt. missing require('./storage/users')
.
Unable to successfully setup osprey.security
with a custom security scheme (also using passport).
I read what @gciatto posted here and the README.md in this repo, but have not been able to create a custom security handler. Please see my sample app posted on issue #117
Prior to using osprey, I could use the
passport-jwt
plugin to require and validate a JSON Web Token for secure routes:Now that I'm using osprey to perform request validation, I would like to instead declare the security in RAML ... :
... and use
osprey.security(...)
to enforce it, but it's unclear to me how to covert my old code:Anybody have an idea on how to do this?