1"use strict";(self.webpackChunkAuthressKnowledgeBase=self.webpackChunkAuthressKnowledgeBase||[]).push([[3166],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>d});var r=n(67294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?a(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):a(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function s(e,t){if(null==e)return{};var n,r,i=function(e,t){if(null==e)return{};var n,r,i={},a=Object.keys(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var l=r.createContext({}),h=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=h(e.components);return r.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},p=r.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),p=h(n),d=i,f=p["".concat(l,".").concat(d)]||p[d]||u[d]||a;return n?r.createElement(f,o(o({ref:t},c),{},{components:n})):r.createElement(f,o({ref:t},c))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,o=new Array(a);o[0]=p;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:i,o[1]=s;for(var h=2;h<a;h++)o[h]=n[h];return r.createElement.apply(null,o)}return r.createElement.apply(null,n)}p.displayName="MDXCreateElement"},23427:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>a,metadata:()=>s,toc:()=>h});var r=n(87462),i=(n(67294),n(3905));const a={hide_table_of_contents:!0,title:"Validating JWTs in Web APIs",authors:"warren-parad",description:"Securing a web application or api requires actually validating the access token that is being used. When using JWTs, there are two mechanisms for doing this.",image:"./assets/how-to-verify-jwt-in-web-app.png",image_alt:"Unlocking JWT security in web apps",date:"2020-11-08T10:00"},o=void 0,s={permalink:"/knowledge-base/articles/how-to-verify-jwt-in-web-app",source:"@site/articles/how-to-verify-jwt-in-web-app.md",title:"Validating JWTs in Web APIs",description:"Securing a web application or api requires actually validating the access token that is being used. When using JWTs, there are two mechanisms for doing this.",date:"2020-11-08T10:00:00.000Z",formattedDate:"November 8, 2020",tags:[],readingTime:2.38,hasTruncateMarker:!1,authors:[{name:"Warren Parad",url:"https://warrenparad.net",page:!0,socials:{bluesky:"wparad.bsky.social",github:"wparad",linkedin:"warren-parad"},imageURL:"https://authress.io/knowledge-base/img/authors/warren-parad.jpg",key:"warren-parad"}],frontMatter:{hide_table_of_contents:!0,title:"Validating JWTs in Web APIs",authors:"warren-parad",description:"Securing a web application or api requires actually validating the access token that is being used. When using JWTs, there are two mechanisms for doing this.",image:"./assets/how-to-verify-jwt-in-web-app.png",image_alt:"Unlocking JWT security in web apps",date:"2020-11-08T10:00"},prevItem:{title:"How to pick the best auth solution",permalink:"/knowledge-base/articles/how-to-pick-best-auth-solution"},nextItem:{title:"How to secure a multitenant application architecture",permalink:"/knowledge-base/articles/creating-a-multitenant-application"}},l={image:n(22020).Z,authorsImageUrls:[void 0]},h=[],c={toc:h};
1function u(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("p",null,"Securing a web application or api requires actually validating the access token that is being used. When using JSON web tokens (JWTs), there are two mechanisms for doing this. But the core of the solution requires inspecting that JWT, understanding who the authority is, and using that authority for verification."),(0,i.kt)("p",null,"The properties or fields in a JWT are called claims. JWTs contain an ",(0,i.kt)("strong",{parentName:"p"},"ISS")," claim. This is the ",(0,i.kt)("strong",{parentName:"p"},"Issuer"),". The issuer is the ",(0,i.kt)("strong",{parentName:"p"},"authorization server")," (AS) which is marked by the issuer. As such the AS provides a full document about how JWTs are constructed and how to verify them. This document must always be found at ",(0,i.kt)("strong",{parentName:"p"},"https://${Issuer}/.well-known/openid-configuration")," (according to ",(0,i.kt)("a",{parentName:"p",href:"https://tools.ietf.org/html/rfc8414"},"RFC 8414"),"). Here\u2019s a ",(0,i.kt)("a",{parentName:"p",href:"https://login.authress.io/.well-known/openid-configuration"},"real life example"),"."),(0,i.kt)("p",null,"What\u2019s more is that the openid configuration may inform you of an ",(0,i.kt)("strong",{parentName:"p"},"introspection")," endpoint. By passing the token there the AS will tell you if the token is a valid one. However, not only is this optional, it is expensive since the results can not be cached. If you are verifying 1000s of tokens per second, it is far too prohibitive to verify them like that."),(0,i.kt)("p",null,"JWTs are signed, that means they have a signature which allows them to be verified. With the signature, they also contain a ",(0,i.kt)("strong",{parentName:"p"},"kid")," which specifies which public key was used to sign the token and create the signature."),(0,i.kt)("p",null,"A better alternative is to use the issuer, kid, and signature to verify the token. To do this get the relevant ",(0,i.kt)("a",{parentName:"p",href:"https://tools.ietf.org/html/rfc7517"},"JSON Web Keys")," (JWK). Use the issuer to get the keys, find the right key using the kid, and then verify the signature using the key. Therefore keys allow you to self verify the token much faster and the keys themselves are cacheable, that means that you can avoid frequent API calls by using the JWKs. This still has a similar problem as the ",(0,i.kt)("strong",{parentName:"p"},"introspect")," endpoint; that is you are implicitly trusting the ",(0,i.kt)("strong",{parentName:"p"},"issuer"),". So step one becomes:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Trust the Issuer - To verify a JWT the first step is to list the issuers that the web application will trust. If you trust all issuers it is trivial for an attacker to create a verifiable token and call your api. Specifying a self-created token which will pass your checks.")),(0,i.kt)("p",null,"After that, verify to token, just break open the token grab associate JWK and verify the signature"),(0,i.kt)("admonition",{type:"info"},(0,i.kt)("p",{parentName:"admonition"},(0,i.kt)("em",{parentName:"p"},"Note: This is a generic authorizer. For Authress specific verifiers, see the ",(0,i.kt)("a",{parentName:"em",href:"/docs/authentication/validating-jwts"},"verifying JWTs")))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js",metastring:'title="Validate request JWT"',title:'"Validate',request:!0,'JWT"':!0},"import axios from 'axios';\nimport { jwtVerify } from 'jose';\nimport jwkConverter from 'jwk-to-pem';\n\nconst ISSUER = 'https://login.authress.io';\nconst PUBLIC_KEY_URL = `${ISSUER}/.well-known/openid-configuration/jwks`;\n\nclass Authorizer {\n async getUser(request) {\n const authorization = Object.keys(request.headers).find(key => {\n return key.match(/^Authorization$/i);\n });\n\n const token = request.headers[authorization] ? request.headers[authorization].split(' ')[1] : null;\n if (!token) {\n throw Error('Unauthorized');\n }\n\n const unverifiedToken = jwtManager.decode(token, { complete: true });
1\n const kid = unverifiedToken && unverifiedToken.header && unverifiedToken.header.kid;\n if (!kid) {\n throw Error('Unauthorized');\n }\n\n const issuer = unverifiedToken && unverifiedToken.payload && unverifiedToken.payload.iss;\n if (!issuer) {\n throw Error('Unauthorized');\n }\n const key = await this.getPublicKey(PUBLIC_KEY_URL, kid);\n\n // highlight-start\n try {\n const pemKey = await importJWK(key);\n const options = { algorithms: ['EdDSA'], issuer };\n const verifiedToken = await jwtVerify(token, pemKey, options);\n return identity.sub;\n } catch (exception) {\n throw Error('Unauthorized');\n }\n // highlight-end\n }\n\n async getPublicKey(jwkKeyListUrl, kid) {\n if (!this.publicKeysPromises[jwkKeyListUrl]) {\n this.publicKeysPromises[jwkKeyListUrl] = axios.get(jwkKeyListUrl);\n }\n\n try {\n const result = await this.publicKeysPromises[jwkKeyListUrl];\n const jwk = result.data.keys.find(key => key.kid === kid);\n if (jwk) {\n return jwkConverter(jwk);\n }\n\n // If the public key isn't found it could be because this token was signed with a new public key, so try fetching a new version\n const retryResult = await axios.get(jwkKeyListUrl);\n const newJwk = retryResult.data.keys.find(key => key.kid === kid);\n if (newJwk) {\n this.publicKeysPromises[jwkKeyListUrl] = retryResult;\n return jwkConverter(newJwk);\n }\n\n // Otherwise this is an old jwk, so fall through and throw Unauthorized Error\n\n } catch (error) {\n // If there is a problem looking up the keys, we have no choice but to return a 401 to the caller.\n // * It's possible that there was a problem connecting to the jwks endpoint. In those cases, adding automatic retries to the HTTP calls here, is recommended\n // * If the retries don't work, and there is still a problem, return a 401 to the caller.\n }\n\n throw Error('Unauthorized');\n }\n}\n\nmodule.exports = Authorizer;\n")))}u.isMDXComponent=!0},22020:(e,t,n)=>{n.d(t,{Z:()=>r});const r=n.p+"assets/images/how-to-verify-jwt-in-web-app-28726f480d960dfb2c26a504f5a73bd0.png"}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.