WIP jwk get and verify
This commit is contained in:
parent
2587d03860
commit
69ce9bf95f
149
oauth3.core.js
149
oauth3.core.js
|
@ -17,6 +17,13 @@
|
||||||
err.code = params.error.code || params.error;
|
err.code = params.error.code || params.error;
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
, create: function (opts) {
|
||||||
|
var err = new Error(opts.message);
|
||||||
|
err.code = opts.code;
|
||||||
|
err.uri = opts.uri || opts.url;
|
||||||
|
err.subErr = opts.subErr;
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
, _binStr: {
|
, _binStr: {
|
||||||
bufferToBinStr: function (buf) {
|
bufferToBinStr: function (buf) {
|
||||||
|
@ -205,6 +212,75 @@
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
, jwk: {
|
||||||
|
get: function (decoded) {
|
||||||
|
return OAUTH3.discover(decoded.payload.iss).then(function (directives) {
|
||||||
|
var urlObj = OAUTH3.jwk.url(directives, decoded);
|
||||||
|
return OAUTH3.request(urlObj).catch(function (err) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: 'failed to retrieve public key from token issuer'
|
||||||
|
, code: 'E_NO_PUB_KEY'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_NO_PUB_KEY'
|
||||||
|
, subErr: err.toString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, function (err) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: 'token issuer is not a valid OAuth3 provider'
|
||||||
|
, code: 'E_INVALID_ISS'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
|
||||||
|
, subErr: err.toString()
|
||||||
|
});
|
||||||
|
}).then(function (res) {
|
||||||
|
if (res.data.error) {
|
||||||
|
return PromiseA.reject(res.data.error);
|
||||||
|
}
|
||||||
|
return res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
, verifyToken: function (token) {
|
||||||
|
var decoded;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: 'no token provided'
|
||||||
|
, code: 'E_NO_TOKEN'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_NO_TOKEN'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
decoded = OAUTH3.jwt.decode(token, {complete: true});
|
||||||
|
} catch (e) {}
|
||||||
|
if (!decoded) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: 'provided token not a JSON Web Token'
|
||||||
|
, code: 'E_NOT_JWT'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_NOT_JWT'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return OAUTH3.jwk.get(decoded).then(function (jwk) {
|
||||||
|
var opts = {};
|
||||||
|
if (Array.isArray(jwk.alg)) {
|
||||||
|
opts.algorithms = jwk.alg;
|
||||||
|
} else if (typeof jwk.alg === 'string') {
|
||||||
|
opts.algorithms = [ jwk.alg ];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return OAUTH3.jwt.verify(token, jwk, opts);
|
||||||
|
} catch (err) {
|
||||||
|
return PromiseA.reject({
|
||||||
|
message: 'token verification failed'
|
||||||
|
, code: 'E_INVALID_TOKEN'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_INVALID_TOKEN'
|
||||||
|
, subErr: err.toString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
, jwt: {
|
, jwt: {
|
||||||
// decode only (no verification)
|
// decode only (no verification)
|
||||||
decode: function (token, opts) {
|
decode: function (token, opts) {
|
||||||
|
@ -228,7 +304,7 @@
|
||||||
, payload: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1]))
|
, payload: JSON.parse(OAUTH3._base64.decodeUrlSafe(parts[1]))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
, verify: function (token, jwk) {
|
, verify: function (token, jwk/*, opts*/) {
|
||||||
if (!OAUTH3.crypto) {
|
if (!OAUTH3.crypto) {
|
||||||
return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable"));
|
return OAUTH3.PromiseA.reject(new Error("OAuth3 crypto library unavailable"));
|
||||||
}
|
}
|
||||||
|
@ -237,9 +313,15 @@
|
||||||
var parts = token.split(/\./g);
|
var parts = token.split(/\./g);
|
||||||
var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.'));
|
var data = OAUTH3._binStr.binStrToBuffer(parts.slice(0, 2).join('.'));
|
||||||
var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]);
|
var signature = OAUTH3._base64.urlSafeToBuffer(parts[2]);
|
||||||
|
var decoded = OAUTH3.jwt.decode(token, { complete: true });
|
||||||
|
|
||||||
|
// TODO disallow none and hmac algorithms
|
||||||
|
// https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
|
||||||
|
if (!decoded.header.alg || 'none' === decoded.header.alg.toString() || /^HS/i.test(decoded.header.alg.toString())) {
|
||||||
|
throw new Error("token algorithm '" + decoded.header.alg + "' is not accepted");
|
||||||
|
}
|
||||||
return OAUTH3.crypto.core.verify(jwk, data, signature).then(function () {
|
return OAUTH3.crypto.core.verify(jwk, data, signature).then(function () {
|
||||||
return OAUTH3.jwt.decode(token);
|
return decoded;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
, sign: function (payload, jwk) {
|
, sign: function (payload, jwk) {
|
||||||
|
@ -335,6 +417,69 @@
|
||||||
opts._pathname = ".well-known/oauth3/scopes.json";
|
opts._pathname = ".well-known/oauth3/scopes.json";
|
||||||
return OAUTH3.urls.rpc(providerUri, opts);
|
return OAUTH3.urls.rpc(providerUri, opts);
|
||||||
}
|
}
|
||||||
|
, jwk: function (directives, decoded) {
|
||||||
|
var sub = decoded.payload.sub || decoded.payload.ppid || decoded.payload.appScopedId;
|
||||||
|
if (!sub) {
|
||||||
|
throw OAUTH3.error.create({
|
||||||
|
message: 'token missing sub'
|
||||||
|
, code: 'E_MISSING_SUB'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_MISSING_SUB'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var kid = decoded.header.kid || decoded.payload.kid;
|
||||||
|
if (!kid) {
|
||||||
|
throw OAUTH3.error.create({
|
||||||
|
message: 'token missing kid'
|
||||||
|
, code: 'E_MISSING_KID'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_MISSING_KID'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!decoded.payload.iss) {
|
||||||
|
throw OAUTH3.error.create({
|
||||||
|
message: 'token missing iss'
|
||||||
|
, code: 'E_MISSING_ISS'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_MISSING_ISS'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = (directives || {}).retrieve_jwk;
|
||||||
|
if (typeof args === 'string') {
|
||||||
|
args = { url: args, method: 'GET' };
|
||||||
|
}
|
||||||
|
if (typeof (args || {}).url !== 'string') {
|
||||||
|
throw OAUTH3.error.create({
|
||||||
|
message: 'token issuer does not support retrieving JWKs'
|
||||||
|
, code: 'E_INVALID_ISS'
|
||||||
|
, url: 'https://oauth3.org/docs/errors#E_INVALID_ISS'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
sub: sub
|
||||||
|
, kid: kid
|
||||||
|
};
|
||||||
|
var url = args.url;
|
||||||
|
var body;
|
||||||
|
Object.keys(params).forEach(function (key) {
|
||||||
|
if (url.indexOf(':'+key) !== -1) {
|
||||||
|
url = url.replace(':'+key, params[key]);
|
||||||
|
delete params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Object.keys(params).length > 0) {
|
||||||
|
if ('GET' === (args.method || 'GET').toUpperCase()) {
|
||||||
|
url += '?' + OAUTH3.query.stringify(params);
|
||||||
|
} else {
|
||||||
|
body = params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: OAUTH3.url.resolve(directives.api, url)
|
||||||
|
, method: args.method
|
||||||
|
, data: body
|
||||||
|
};
|
||||||
|
}
|
||||||
, implicitGrant: function (directive, opts) {
|
, implicitGrant: function (directive, opts) {
|
||||||
//
|
//
|
||||||
// Example Implicit Grant Request
|
// Example Implicit Grant Request
|
||||||
|
|
Loading…
Reference in New Issue