swagger-node
swagger-node copied to clipboard
JWT security and scopes - my working solution
Hello, here there is my solution, hope I can help other developers!
Token
Lets us imagine that the APP will receive in the header a JWT token (TOKENVALUE). The token has been sign with a private key. Payload can be for example:
{ user:
{ system_name: 'SVJWTDEV',
user_uid: 'root',
name: 'Supervisor',
surname: 'Supervisor',
email: '[email protected]' },
scopes: [ 'admin' ],
custom: {},
exp: 1489696621,
iat: 1489675021 }
In my case, the scope is just an array of roles
Swagger.yaml
...
securityDefinitions:
APIKey:
description: "Accesso tramite JWT"
type: "apiKey"
name: "Authorization"
in: "header"
...
paths:
/protected_calls:
get:
security:
- APIKey: []
x-security-scopes:
- admin
I have extended swagger adding x-security-scopes. This is the key point of the solution.
Client authorization
Every calls to server should contains
Authorization: Bearer TOKENVALUE
Middleware
In the swaggerSecurity function it is now easy to verify the token using the public key and check if there is an intersection between scopes from token and x-security-scopes I'm a newbie to nodejs so just get the idea and not the specific implementation
app.use(middleware.swaggerSecurity({
APIKey: function(req, def, JWTAuth, callback) {
var current_req_scopes = req.swagger.operation["x-security-scopes"]
if (!!JWTAuth && JWTAuth.indexOf("Bearer ") == 0) {
var JWTToken = JWTAuth.split("Bearer ")[1]
jwt.verify(JWTToken, appl_config.jwt.pubKey, function(err, payload) {
if (err) {
var err = new Error('Invalid token');
err['statusCode'] = 400;
callback(err);
return
}
if (_.intersection(payload.scopes, current_req_scopes).length == 0) {
console.log("Not Authorized!")
var err = new Error('Not Authorized');
err['statusCode'] = 401;
callback(err);
return
}
else {
console.log("Authorized!")
req.swagger.params.auth_payload = payload; //example
callback()
}
});
} else {
var err = new Error('Failed to authenticate using bearer token');
err['statusCode'] = 403; // custom error code
callback(err);
}
},
}));
Riccardo
Great solution, thanks for sharing Riccardo!
Only problem is it can't be documented through swagger-ui...
We also came up with a similar solution. On top of that, we have added a version check to expire any existing tokens, when needed, to force a re-authentication.
if(token.version !== config.jwt.version) {
// return 403
}
@ric79 Thanks for the great post.
What if you have a multiple security definition?
securityDefinitions:
APIKey1:
description: "Accesso tramite JWT"
type: "apiKey"
name: "Authorization"
in: "header"
APIKey2:
description: "another key"
type: "apiKey"
name: "another key"
in: header
...
paths:
/protected_calls:
get:
security:
- APIKey: []
APIKey2: [] # Here, the endpoint uses both keys
x-security-scopes:
- admin
...
And your authentication mw uses both keys? What do you use instead of ??? ?
app.use(middleware.swaggerSecurity({
???: function(req, def, JWTAuth, callback) {
....
Did someone use this solution using Python and the Connexion module?
I don't know how to access req.swagger.operation["x-security-scopes"]
@ric79 Thanks for the great post.
What if you have a multiple security definition?
securityDefinitions: APIKey1: description: "Accesso tramite JWT" type: "apiKey" name: "Authorization" in: "header" APIKey2: description: "another key" type: "apiKey" name: "another key" in: header ... paths: /protected_calls: get: security: - APIKey: [] APIKey2: [] # Here, the endpoint uses both keys x-security-scopes: - admin ...
And your authentication mw uses both keys? What do you use instead of ??? ?
app.use(middleware.swaggerSecurity({ ???: function(req, def, JWTAuth, callback) { ....
Something like this?
securityDefinitions:
adminKey:
type: apiKey
in: header
name: Authorization
userKey:
type: apiKey
in: header
name: Authorization
const { authorize, authorizeAdmin } = require('./api/helpers/auth');
app.use(middleware.swaggerSecurity({
userKey: authorize,
adminKey: authorizeAdmin,
}));