fetch-mw-oauth2
Advanced tools
Comparing version 2.0.7 to 2.0.8
@@ -1,2 +0,2 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.fetchMwOAuth2=t():e.fetchMwOAuth2=t()}(self,(()=>(()=>{"use strict";var e={934:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.generateQueryString=t.tokenResponseToOAuth2Token=t.OAuth2Client=void 0;const n=r(443),i=r(618);function o(e,t){return new URL(e,t).toString()}function s(e){return e.then((e=>{var t;return{accessToken:e.access_token,expiresAt:e.expires_in?Date.now()+1e3*e.expires_in:null,refreshToken:null!==(t=e.refresh_token)&&void 0!==t?t:null}}))}function a(e){return new URLSearchParams(Object.fromEntries(Object.entries(e).filter((([e,t])=>void 0!==t)))).toString()}t.OAuth2Client=class{constructor(e){this.discoveryDone=!1,this.serverMetadata=null,this.settings=e}async refreshToken(e){if(!e.refreshToken)throw new Error("This token didn't have a refreshToken. It's not possible to refresh this");const t={grant_type:"refresh_token",refresh_token:e.refreshToken};return this.settings.clientSecret||(t.client_id=this.settings.clientId),s(this.request("tokenEndpoint",t))}async clientCredentials(e){var t;const r={grant_type:"client_credentials",scope:null===(t=null==e?void 0:e.scope)||void 0===t?void 0:t.join(" ")};if(!this.settings.clientSecret)throw new Error("A clientSecret must be provied to use client_credentials");return s(this.request("tokenEndpoint",r))}async password(e){var t;const r={grant_type:"password",...e,scope:null===(t=e.scope)||void 0===t?void 0:t.join(" ")};if(!this.settings.clientSecret)throw new Error("A clientSecret must be provied to use client_credentials");return s(this.request("tokenEndpoint",r))}authorizationCode(e){return new i.OAuth2AuthorizationCodeClient(this,e.redirectUri,e.state)}async introspect(e){const t={token:e.accessToken,token_type_hint:"access_token"};return this.request("introspectionEndpoint",t)}async getEndpoint(e){if(void 0!==this.settings[e])return o(this.settings[e],this.settings.server);if("discoveryEndpoint"!==e&&(await this.discover(),void 0!==this.settings[e]))return o(this.settings[e],this.settings.server);if(!this.settings.server)throw new Error(`Could not determine the location of ${e}. Either specify ${e} in the settings, or the "server" endpoint to let the client discover it.`);switch(e){case"authorizationEndpoint":return o("/authorize",this.settings.server);case"tokenEndpoint":return o("/token",this.settings.server);case"discoveryEndpoint":return o("/.well-known/oauth-authorization-server",this.settings.server);case"introspectionEndpoint":return o("/introspect",this.settings.server)}}async discover(){if(this.discoveryDone)return;let e;this.discoveryDone=!0;try{e=await this.getEndpoint("discoveryEndpoint")}catch(e){return void console.warn('[oauth2] OAuth2 discovery endpoint could not be determined. Either specify the "server" or "discoveryEndpoint')}const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)return;if(!t.headers.has("Content-Type")||t.headers.get("Content-Type").startsWith("application/json"))return void console.warn("[oauth2] OAuth2 discovery endpoint was not a JSON response. Response is ignored");this.serverMetadata=await t.json();const r=[["authorization_endpoint","authorizationEndpoint"],["token_endpoint","tokenEndpoint"],["introspection_endpoint","introspectionEndpoint"]];if(null!==this.serverMetadata)for(const[t,n]of r)this.serverMetadata[t]&&(this.settings[n]=o(this.serverMetadata[t],e))}async request(e,t){const r=await this.getEndpoint(e),i={};if("authorization_code"!==t.grant_type&&this.settings.clientSecret){const e=btoa(this.settings.clientId+":"+this.settings.clientSecret);i.Authorization="Basic "+e}const o=await fetch(r,{method:"POST",body:a(t),headers:i});if(o.ok)return await o.json();let s,h,c;throw o.headers.has("Content-Type")&&o.headers.get("Content-Type").startsWith("application/json")&&(s=await o.json()),(null==s?void 0:s.error)?(h="OAuth2 error "+s.error+".",s.error_description&&(h+=" "+s.error_description),c=s.error):(h="HTTP Error "+o.status+" "+o.statusText,401===o.status&&this.settings.clientSecret&&(h+=". It's likely that the clientId and/or clientSecret was incorrect"),c=null),new n.OAuth2Error(h,c,o.status)}},t.tokenResponseToOAuth2Token=s,t.generateQueryString=a},618:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.generateCodeVerifier=t.OAuth2AuthorizationCodeClient=void 0;const n=r(934),i=r(443);async function o(e){return["S256",a(await crypto.subtle.digest("SHA-256",s(e)))]}function s(e){const t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=255&e.charCodeAt(r);return t}function a(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}t.OAuth2AuthorizationCodeClient=class{constructor(e,t,r,n){this.client=e,this.redirectUri=t,this.state=r,this.codeVerifier=n}async getAuthorizeUri(){const[e,t]=await Promise.all([this.codeVerifier?o(this.codeVerifier):void 0,this.client.getEndpoint("authorizationEndpoint")]),r={response_type:"code",client_id:this.client.settings.clientId,redirect_uri:this.redirectUri,code_challenge_method:null==e?void 0:e[0],code_challenge:null==e?void 0:e[1]};return this.state&&(r.state=this.state),t+"?"+(0,n.generateQueryString)(r)}async validateResponse(e){var t;const r=new URL(e).searchParams;if(r.has("error"))throw new i.OAuth2Error(null!==(t=r.get("error_description"))&&void 0!==t?t:"OAuth2 error",r.get("error"),0);if(!r.has("code"))throw new Error(`The url did not contain a code parameter ${e}`);if(!r.has("state"))throw new Error(`The url did not contain state parameter ${e}`);if(this.state!==r.get("state"))throw new Error(`The "state" parameter in the url did not match the expected value of ${this.state}`);return{code:r.get("code")}}async getToken(e){const t={grant_type:"authorization_code",code:e.code,redirect_uri:this.redirectUri,client_id:this.client.settings.clientId,code_verifier:this.codeVerifier};return(0,n.tokenResponseToOAuth2Token)(this.client.request("tokenEndpoint",t))}},t.generateCodeVerifier=function(){const e=new Uint8Array(32);return crypto.getRandomValues(e),a(e)}},443:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OAuth2Error=void 0;class r extends Error{constructor(e,t,r){super(e),this.oauth2Code=t,this.httpCode=r}}t.OAuth2Error=r},13:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OAuth2Fetch=void 0,t.OAuth2Fetch=class{constructor(e){this.token=null,this.activeRefresh=null,this.refreshTimer=null,this.options=e,e.getStoredToken&&(async()=>{this.token=await e.getStoredToken()})(),this.scheduleRefresh()}async fetch(e,t){const r=new Request(e,t);return this.mw()(r,(e=>fetch(e)))}mw(){return async(e,t)=>{const r=await this.getAccessToken();let n=e.clone();n.headers.set("Authorization","Bearer "+r);let i=await t(n);if(!i.ok&&401===i.status){const r=await this.refreshToken();n=e.clone(),n.headers.set("Authorization","Bearer "+r.accessToken),i=await t(n)}return i}}async getToken(){return this.token&&(null===this.token.expiresAt||this.token.expiresAt>Date.now())?this.token:this.refreshToken()}async getAccessToken(){return(await this.getToken()).accessToken}async refreshToken(){var e,t;if(this.activeRefresh)return this.activeRefresh;const r=this.token;this.activeRefresh=(async()=>{var e,t;let n=null;try{(null==r?void 0:r.refreshToken)&&(n=await this.options.client.refreshToken(r))}catch(e){console.warn("[oauth2] refresh token not accepted, we'll try reauthenticating")}if(n||(n=await this.options.getNewToken()),!n){const r=new Error("Unableto obtain OAuth2 tokens, a full reauth may be needed");throw null===(t=(e=this.options).onError)||void 0===t||t.call(e,r),r}return n})();try{const r=await this.activeRefresh;return this.token=r,null===(t=(e=this.options).storeToken)||void 0===t||t.call(e,r),this.scheduleRefresh(),r}catch(e){throw this.options.onError&&this.options.onError(e),e}finally{this.activeRefresh=null}}scheduleRefresh(){if(this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),!this.token||!this.token.expiresAt||!this.token.refreshToken)return;const e=this.token.expiresAt-Date.now();e<12e4||(this.refreshTimer=setTimeout((async()=>{try{await this.refreshToken()}catch(e){console.error("[fetch-mw-oauth2] error while doing a background OAuth2 auto-refresh",e)}}),e-6e4))}}}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}var n={};return(()=>{var e=n;Object.defineProperty(e,"__esModule",{value:!0}),e.OAuth2Error=e.OAuth2Fetch=e.generateCodeVerifier=e.OAuth2AuthorizationCodeClient=e.OAuth2Client=void 0;var t=r(934);Object.defineProperty(e,"OAuth2Client",{enumerable:!0,get:function(){return t.OAuth2Client}});var i=r(618);Object.defineProperty(e,"OAuth2AuthorizationCodeClient",{enumerable:!0,get:function(){return i.OAuth2AuthorizationCodeClient}}),Object.defineProperty(e,"generateCodeVerifier",{enumerable:!0,get:function(){return i.generateCodeVerifier}});var o=r(13);Object.defineProperty(e,"OAuth2Fetch",{enumerable:!0,get:function(){return o.OAuth2Fetch}});var s=r(443);Object.defineProperty(e,"OAuth2Error",{enumerable:!0,get:function(){return s.OAuth2Error}})})(),n})())); | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.fetchMwOAuth2=t():e.fetchMwOAuth2=t()}(self,(()=>(()=>{"use strict";var e={934:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.generateQueryString=t.tokenResponseToOAuth2Token=t.OAuth2Client=void 0;const n=r(443),i=r(618);function o(e,t){return new URL(e,t).toString()}function s(e){return e.then((e=>{var t;return{accessToken:e.access_token,expiresAt:e.expires_in?Date.now()+1e3*e.expires_in:null,refreshToken:null!==(t=e.refresh_token)&&void 0!==t?t:null}}))}function a(e){return new URLSearchParams(Object.fromEntries(Object.entries(e).filter((([e,t])=>void 0!==t)))).toString()}t.OAuth2Client=class{constructor(e){this.discoveryDone=!1,this.serverMetadata=null,this.settings=e}async refreshToken(e){if(!e.refreshToken)throw new Error("This token didn't have a refreshToken. It's not possible to refresh this");const t={grant_type:"refresh_token",refresh_token:e.refreshToken};return this.settings.clientSecret||(t.client_id=this.settings.clientId),s(this.request("tokenEndpoint",t))}async clientCredentials(e){var t;const r={grant_type:"client_credentials",scope:null===(t=null==e?void 0:e.scope)||void 0===t?void 0:t.join(" ")};if(!this.settings.clientSecret)throw new Error("A clientSecret must be provied to use client_credentials");return s(this.request("tokenEndpoint",r))}async password(e){var t;const r={grant_type:"password",...e,scope:null===(t=e.scope)||void 0===t?void 0:t.join(" ")};if(!this.settings.clientSecret)throw new Error("A clientSecret must be provied to use client_credentials");return s(this.request("tokenEndpoint",r))}get authorizationCode(){return new i.OAuth2AuthorizationCodeClient(this)}async introspect(e){const t={token:e.accessToken,token_type_hint:"access_token"};return this.request("introspectionEndpoint",t)}async getEndpoint(e){if(void 0!==this.settings[e])return o(this.settings[e],this.settings.server);if("discoveryEndpoint"!==e&&(await this.discover(),void 0!==this.settings[e]))return o(this.settings[e],this.settings.server);if(!this.settings.server)throw new Error(`Could not determine the location of ${e}. Either specify ${e} in the settings, or the "server" endpoint to let the client discover it.`);switch(e){case"authorizationEndpoint":return o("/authorize",this.settings.server);case"tokenEndpoint":return o("/token",this.settings.server);case"discoveryEndpoint":return o("/.well-known/oauth-authorization-server",this.settings.server);case"introspectionEndpoint":return o("/introspect",this.settings.server)}}async discover(){if(this.discoveryDone)return;let e;this.discoveryDone=!0;try{e=await this.getEndpoint("discoveryEndpoint")}catch(e){return void console.warn('[oauth2] OAuth2 discovery endpoint could not be determined. Either specify the "server" or "discoveryEndpoint')}const t=await fetch(e,{headers:{Accept:"application/json"}});if(!t.ok)return;if(!t.headers.has("Content-Type")||t.headers.get("Content-Type").startsWith("application/json"))return void console.warn("[oauth2] OAuth2 discovery endpoint was not a JSON response. Response is ignored");this.serverMetadata=await t.json();const r=[["authorization_endpoint","authorizationEndpoint"],["token_endpoint","tokenEndpoint"],["introspection_endpoint","introspectionEndpoint"]];if(null!==this.serverMetadata)for(const[t,n]of r)this.serverMetadata[t]&&(this.settings[n]=o(this.serverMetadata[t],e))}async request(e,t){const r=await this.getEndpoint(e),i={};if("authorization_code"!==t.grant_type&&this.settings.clientSecret){const e=btoa(this.settings.clientId+":"+this.settings.clientSecret);i.Authorization="Basic "+e}const o=await fetch(r,{method:"POST",body:a(t),headers:i});if(o.ok)return await o.json();let s,c,h;throw o.headers.has("Content-Type")&&o.headers.get("Content-Type").startsWith("application/json")&&(s=await o.json()),(null==s?void 0:s.error)?(c="OAuth2 error "+s.error+".",s.error_description&&(c+=" "+s.error_description),h=s.error):(c="HTTP Error "+o.status+" "+o.statusText,401===o.status&&this.settings.clientSecret&&(c+=". It's likely that the clientId and/or clientSecret was incorrect"),h=null),new n.OAuth2Error(c,h,o.status)}},t.tokenResponseToOAuth2Token=s,t.generateQueryString=a},618:(e,t,r)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.generateCodeVerifier=t.OAuth2AuthorizationCodeClient=void 0;const n=r(934),i=r(443);async function o(e){return["S256",a(await crypto.subtle.digest("SHA-256",s(e)))]}function s(e){const t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=255&e.charCodeAt(r);return t}function a(e){return btoa(String.fromCharCode(...new Uint8Array(e))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}t.OAuth2AuthorizationCodeClient=class{constructor(e){this.client=e}async getAuthorizeUri(e){const[t,r]=await Promise.all([e.codeVerifier?o(e.codeVerifier):void 0,this.client.getEndpoint("authorizationEndpoint")]),i={response_type:"code",client_id:this.client.settings.clientId,redirect_uri:e.redirectUri,code_challenge_method:null==t?void 0:t[0],code_challenge:null==t?void 0:t[1]};return e.state&&(i.state=e.state),r+"?"+(0,n.generateQueryString)(i)}async getTokenFromCodeRedirect(e,t){const{code:r}=await this.validateResponse(e,{state:t.state});return this.getToken({code:r,redirectUri:t.redirectUri,codeVerifier:t.codeVerifier})}async validateResponse(e,t){var r;const n=new URL(e).searchParams;if(n.has("error"))throw new i.OAuth2Error(null!==(r=n.get("error_description"))&&void 0!==r?r:"OAuth2 error",n.get("error"),0);if(!n.has("code"))throw new Error(`The url did not contain a code parameter ${e}`);if(!n.has("state"))throw new Error(`The url did not contain state parameter ${e}`);if(t.state&&t.state!==n.get("state"))throw new Error(`The "state" parameter in the url did not match the expected value of ${t.state}`);return{code:n.get("code")}}async getToken(e){const t={grant_type:"authorization_code",code:e.code,redirect_uri:e.redirectUri,client_id:this.client.settings.clientId,code_verifier:e.codeVerifier};return(0,n.tokenResponseToOAuth2Token)(this.client.request("tokenEndpoint",t))}},t.generateCodeVerifier=function(){const e=new Uint8Array(32);return crypto.getRandomValues(e),a(e)}},443:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OAuth2Error=void 0;class r extends Error{constructor(e,t,r){super(e),this.oauth2Code=t,this.httpCode=r}}t.OAuth2Error=r},13:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OAuth2Fetch=void 0,t.OAuth2Fetch=class{constructor(e){this.token=null,this.activeRefresh=null,this.refreshTimer=null,this.options=e,e.getStoredToken&&(async()=>{this.token=await e.getStoredToken()})(),this.scheduleRefresh()}async fetch(e,t){const r=new Request(e,t);return this.mw()(r,(e=>fetch(e)))}mw(){return async(e,t)=>{const r=await this.getAccessToken();let n=e.clone();n.headers.set("Authorization","Bearer "+r);let i=await t(n);if(!i.ok&&401===i.status){const r=await this.refreshToken();n=e.clone(),n.headers.set("Authorization","Bearer "+r.accessToken),i=await t(n)}return i}}async getToken(){return this.token&&(null===this.token.expiresAt||this.token.expiresAt>Date.now())?this.token:this.refreshToken()}async getAccessToken(){return(await this.getToken()).accessToken}async refreshToken(){var e,t;if(this.activeRefresh)return this.activeRefresh;const r=this.token;this.activeRefresh=(async()=>{var e,t;let n=null;try{(null==r?void 0:r.refreshToken)&&(n=await this.options.client.refreshToken(r))}catch(e){console.warn("[oauth2] refresh token not accepted, we'll try reauthenticating")}if(n||(n=await this.options.getNewToken()),!n){const r=new Error("Unableto obtain OAuth2 tokens, a full reauth may be needed");throw null===(t=(e=this.options).onError)||void 0===t||t.call(e,r),r}return n})();try{const r=await this.activeRefresh;return this.token=r,null===(t=(e=this.options).storeToken)||void 0===t||t.call(e,r),this.scheduleRefresh(),r}catch(e){throw this.options.onError&&this.options.onError(e),e}finally{this.activeRefresh=null}}scheduleRefresh(){if(this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null),!this.token||!this.token.expiresAt||!this.token.refreshToken)return;const e=this.token.expiresAt-Date.now();e<12e4||(this.refreshTimer=setTimeout((async()=>{try{await this.refreshToken()}catch(e){console.error("[fetch-mw-oauth2] error while doing a background OAuth2 auto-refresh",e)}}),e-6e4))}}}},t={};function r(n){var i=t[n];if(void 0!==i)return i.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}var n={};return(()=>{var e=n;Object.defineProperty(e,"__esModule",{value:!0}),e.OAuth2Error=e.OAuth2Fetch=e.generateCodeVerifier=e.OAuth2AuthorizationCodeClient=e.OAuth2Client=void 0;var t=r(934);Object.defineProperty(e,"OAuth2Client",{enumerable:!0,get:function(){return t.OAuth2Client}});var i=r(618);Object.defineProperty(e,"OAuth2AuthorizationCodeClient",{enumerable:!0,get:function(){return i.OAuth2AuthorizationCodeClient}}),Object.defineProperty(e,"generateCodeVerifier",{enumerable:!0,get:function(){return i.generateCodeVerifier}});var o=r(13);Object.defineProperty(e,"OAuth2Fetch",{enumerable:!0,get:function(){return o.OAuth2Fetch}});var s=r(443);Object.defineProperty(e,"OAuth2Error",{enumerable:!0,get:function(){return s.OAuth2Error}})})(),n})())); | ||
//# sourceMappingURL=fetch-mw-oauth2.min.js.map |
@@ -78,7 +78,8 @@ import { OAuth2Token } from './token'; | ||
}): Promise<OAuth2Token>; | ||
authorizationCode(params: { | ||
redirectUri: string; | ||
state: string; | ||
}): OAuth2AuthorizationCodeClient; | ||
/** | ||
* Returns the helper object for the `authorization_code` grant. | ||
* | ||
*/ | ||
get authorizationCode(): OAuth2AuthorizationCodeClient; | ||
/** | ||
* Introspect a token | ||
@@ -85,0 +86,0 @@ * |
@@ -58,4 +58,8 @@ "use strict"; | ||
} | ||
authorizationCode(params) { | ||
return new authorization_code_1.OAuth2AuthorizationCodeClient(this, params.redirectUri, params.state); | ||
/** | ||
* Returns the helper object for the `authorization_code` grant. | ||
* | ||
*/ | ||
get authorizationCode() { | ||
return new authorization_code_1.OAuth2AuthorizationCodeClient(this); | ||
} | ||
@@ -62,0 +66,0 @@ /** |
@@ -5,6 +5,3 @@ import { OAuth2Client } from '../client'; | ||
client: OAuth2Client; | ||
redirectUri: string; | ||
state: string | undefined; | ||
codeVerifier: string | undefined; | ||
constructor(client: OAuth2Client, redirectUri: string, state?: string, codeVerifier?: string); | ||
constructor(client: OAuth2Client); | ||
/** | ||
@@ -14,3 +11,12 @@ * Returns the URi that the user should open in a browser to initiate the | ||
*/ | ||
getAuthorizeUri(): Promise<string>; | ||
getAuthorizeUri(params: { | ||
redirectUri: string; | ||
state?: string; | ||
codeVerifier?: string; | ||
}): Promise<string>; | ||
getTokenFromCodeRedirect(url: string | URL, params: { | ||
redirectUri: string; | ||
state?: string; | ||
codeVerifier?: string; | ||
}): Promise<OAuth2Token>; | ||
/** | ||
@@ -23,5 +29,6 @@ * After the user redirected back from the authorization endpoint, the | ||
*/ | ||
validateResponse(url: string | URL): Promise<{ | ||
validateResponse(url: string | URL, params: { | ||
state?: string; | ||
}): Promise<{ | ||
code: string; | ||
codeVerifier?: string; | ||
}>; | ||
@@ -33,4 +40,6 @@ /** | ||
code: string; | ||
redirectUri: string; | ||
codeVerifier?: string; | ||
}): Promise<OAuth2Token>; | ||
} | ||
export declare function generateCodeVerifier(): string; |
@@ -7,7 +7,4 @@ "use strict"; | ||
class OAuth2AuthorizationCodeClient { | ||
constructor(client, redirectUri, state, codeVerifier) { | ||
constructor(client) { | ||
this.client = client; | ||
this.redirectUri = redirectUri; | ||
this.state = state; | ||
this.codeVerifier = codeVerifier; | ||
} | ||
@@ -18,5 +15,5 @@ /** | ||
*/ | ||
async getAuthorizeUri() { | ||
async getAuthorizeUri(params) { | ||
const [codeChallenge, authorizationEndpoint] = await Promise.all([ | ||
this.codeVerifier ? getCodeChallenge(this.codeVerifier) : undefined, | ||
params.codeVerifier ? getCodeChallenge(params.codeVerifier) : undefined, | ||
this.client.getEndpoint('authorizationEndpoint') | ||
@@ -27,11 +24,21 @@ ]); | ||
client_id: this.client.settings.clientId, | ||
redirect_uri: this.redirectUri, | ||
redirect_uri: params.redirectUri, | ||
code_challenge_method: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[0], | ||
code_challenge: codeChallenge === null || codeChallenge === void 0 ? void 0 : codeChallenge[1], | ||
}; | ||
if (this.state) { | ||
query.state = this.state; | ||
if (params.state) { | ||
query.state = params.state; | ||
} | ||
return authorizationEndpoint + '?' + (0, client_1.generateQueryString)(query); | ||
} | ||
async getTokenFromCodeRedirect(url, params) { | ||
const { code } = await this.validateResponse(url, { | ||
state: params.state | ||
}); | ||
return this.getToken({ | ||
code, | ||
redirectUri: params.redirectUri, | ||
codeVerifier: params.codeVerifier, | ||
}); | ||
} | ||
/** | ||
@@ -44,3 +51,3 @@ * After the user redirected back from the authorization endpoint, the | ||
*/ | ||
async validateResponse(url) { | ||
async validateResponse(url, params) { | ||
var _a; | ||
@@ -55,4 +62,4 @@ const queryParams = new URL(url).searchParams; | ||
throw new Error(`The url did not contain state parameter ${url}`); | ||
if (this.state !== queryParams.get('state')) { | ||
throw new Error(`The "state" parameter in the url did not match the expected value of ${this.state}`); | ||
if (params.state && params.state !== queryParams.get('state')) { | ||
throw new Error(`The "state" parameter in the url did not match the expected value of ${params.state}`); | ||
} | ||
@@ -70,5 +77,5 @@ return { | ||
code: params.code, | ||
redirect_uri: this.redirectUri, | ||
redirect_uri: params.redirectUri, | ||
client_id: this.client.settings.clientId, | ||
code_verifier: this.codeVerifier, | ||
code_verifier: params.codeVerifier, | ||
}; | ||
@@ -75,0 +82,0 @@ return (0, client_1.tokenResponseToOAuth2Token)(this.client.request('tokenEndpoint', body)); |
{ | ||
"name": "fetch-mw-oauth2", | ||
"version": "2.0.7", | ||
"version": "2.0.8", | ||
"description": "Fetch middleware to add OAuth2 support", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -122,7 +122,7 @@ # fetch-mw-oauth2 | ||
This library provides support for all 3 steps, but there's no requirement | ||
This library provides support for these steps, but there's no requirement | ||
to use its functionality as the system is mostly stateless. | ||
```typescript | ||
import { OAuth2Client } from 'client'; | ||
import { OAuth2Client, generateCodeVerifier } from 'client'; | ||
@@ -137,12 +137,2 @@ const client = new OAuth2Client({ | ||
}); | ||
const authorizationCode = client.authorizationCode({ | ||
// URL in the app that the user should get redirected to after authenticating | ||
redirectUri: 'https://my-app.example/', | ||
// Optional string that can be sent along to the auth server. This value will | ||
// be sent along with the redirect back to the app verbatim. | ||
state: 'some-string', | ||
}); | ||
``` | ||
@@ -153,50 +143,17 @@ | ||
```typescript | ||
// In a browser this might work as follows: | ||
document.location = await authorizationCode.getAuthorizeUri(); | ||
``` | ||
**Handling the redirect back to the app and obtain token** | ||
```typescript | ||
const codeResponse = await authorizationCode.validateResponse( | ||
document.location | ||
); | ||
const oauth2Token = await authorizationCode.getToken(codeResponse); | ||
``` | ||
### PKCE support | ||
Modern OAuth2 server should support PKCE, which improves security. | ||
This library supports PKCE. Luckily you don't need to know in advance whether | ||
your authorization server supports it. If they do, you get the additional | ||
benefit. If not, nothing should break. | ||
To use PKCE, you need to make one extra step when calling `authorizationCode`: | ||
```typescript | ||
import { OAuth2Client, getCodeVerifier } from 'client'; | ||
const client = new OAuth2Client({ | ||
server: 'https://authserver.example/', | ||
clientId: '...', | ||
// Note, if urls cannot be auto-detected, also specify these: | ||
tokenEndpoint: '/token', | ||
authorizationEndpoint: '/authorize', | ||
}); | ||
/** | ||
* IMPORTANT! This returns a random value every time it's called | ||
* This generates a security code that must be passed to the various steps. | ||
* This is used for 'PKCE' which is an advanced security feature. | ||
* | ||
* Because the authorization_code is a multi-step process that likely results | ||
* in the user leaving your website and coming back later, you must store the | ||
* result of this somewhere. | ||
* It doesn't break servers that don't support it, but it makes servers that | ||
* so support it more secure. | ||
* | ||
* The codeVerifier gets used in the first step 'getAuthorizeUrl()` and the | ||
* last step 'getToken()`. | ||
* It's optional to pass this, but recommended. | ||
*/ | ||
const codeVerifier = getCodeVerifier(); | ||
const authorizationCode = client.authorizationCode({ | ||
const codeVerifier = generateCodeVerifier(): | ||
// In a browser this might work as follows: | ||
document.location = await authorizationCode.authorizationCode.getAuthorizeUri({ | ||
// URL in the app that the user should get redirected to after authenticating | ||
@@ -209,7 +166,36 @@ redirectUri: 'https://my-app.example/', | ||
// Pass the code verifier | ||
codeVerifier, | ||
}); | ||
``` | ||
**Handling the redirect back to the app and obtain token** | ||
```typescript | ||
const oauth2Token = await client.authorizationCode.getTokenFromCodeRedirect( | ||
document.location, | ||
{ | ||
/** | ||
* The redirect URI is not actually used for any redirects, but MUST be the | ||
* same as what you passed earlier to "authorizationCode" | ||
*/ | ||
redirectUri: 'https://my-app.example/', | ||
/** | ||
* This is optional, but if it's passed then it also MUST be the same as | ||
* what you passed in the first step. | ||
* | ||
* If set, it will verify that the server sent the exact same state back. | ||
*/ | ||
state: 'some-string', | ||
codeVerifier, | ||
} | ||
); | ||
const oauth2Token = await authorizationCode.getToken(codeResponse); | ||
``` | ||
### Fetch Wrapper | ||
@@ -249,3 +235,3 @@ | ||
// Example | ||
return client.clientCredentials(); | ||
return client.clientCredentials(); | ||
@@ -252,0 +238,0 @@ // Another example |
@@ -145,8 +145,10 @@ import { OAuth2Token } from './token'; | ||
authorizationCode(params: {redirectUri: string; state: string}): OAuth2AuthorizationCodeClient { | ||
/** | ||
* Returns the helper object for the `authorization_code` grant. | ||
* | ||
*/ | ||
get authorizationCode(): OAuth2AuthorizationCodeClient { | ||
return new OAuth2AuthorizationCodeClient( | ||
this, | ||
params.redirectUri, | ||
params.state, | ||
); | ||
@@ -153,0 +155,0 @@ |
@@ -9,12 +9,6 @@ import { OAuth2Client, tokenResponseToOAuth2Token, generateQueryString } from '../client'; | ||
client: OAuth2Client; | ||
redirectUri: string; | ||
state: string|undefined; | ||
codeVerifier: string|undefined; | ||
constructor(client: OAuth2Client, redirectUri: string, state?: string, codeVerifier?: string) { | ||
constructor(client: OAuth2Client) { | ||
this.client = client; | ||
this.redirectUri = redirectUri; | ||
this.state = state; | ||
this.codeVerifier = codeVerifier; | ||
@@ -27,3 +21,3 @@ } | ||
*/ | ||
async getAuthorizeUri(): Promise<string> { | ||
async getAuthorizeUri(params: {redirectUri: string; state?: string; codeVerifier?: string}): Promise<string> { | ||
@@ -34,3 +28,3 @@ const [ | ||
] = await Promise.all([ | ||
this.codeVerifier ? getCodeChallenge(this.codeVerifier) : undefined, | ||
params.codeVerifier ? getCodeChallenge(params.codeVerifier) : undefined, | ||
this.client.getEndpoint('authorizationEndpoint') | ||
@@ -42,8 +36,8 @@ ]); | ||
client_id: this.client.settings.clientId, | ||
redirect_uri: this.redirectUri, | ||
redirect_uri: params.redirectUri, | ||
code_challenge_method: codeChallenge?.[0], | ||
code_challenge: codeChallenge?.[1], | ||
}; | ||
if (this.state) { | ||
query.state = this.state; | ||
if (params.state) { | ||
query.state = params.state; | ||
} | ||
@@ -55,2 +49,16 @@ | ||
async getTokenFromCodeRedirect(url: string|URL, params: {redirectUri: string; state?: string; codeVerifier?:string} ): Promise<OAuth2Token> { | ||
const { code } = await this.validateResponse(url, { | ||
state: params.state | ||
}); | ||
return this.getToken({ | ||
code, | ||
redirectUri: params.redirectUri, | ||
codeVerifier: params.codeVerifier, | ||
}); | ||
} | ||
/** | ||
@@ -63,3 +71,3 @@ * After the user redirected back from the authorization endpoint, the | ||
*/ | ||
async validateResponse(url: string|URL): Promise<{code: string; codeVerifier?: string}> { | ||
async validateResponse(url: string|URL, params: {state?: string}): Promise<{code: string}> { | ||
@@ -79,4 +87,4 @@ const queryParams = new URL(url).searchParams; | ||
if (this.state !== queryParams.get('state')) { | ||
throw new Error(`The "state" parameter in the url did not match the expected value of ${this.state}`); | ||
if (params.state && params.state !== queryParams.get('state')) { | ||
throw new Error(`The "state" parameter in the url did not match the expected value of ${params.state}`); | ||
} | ||
@@ -94,3 +102,3 @@ | ||
*/ | ||
async getToken(params: { code: string }): Promise<OAuth2Token> { | ||
async getToken(params: { code: string; redirectUri: string; codeVerifier?: string }): Promise<OAuth2Token> { | ||
@@ -100,5 +108,5 @@ const body:AuthorizationCodeRequest = { | ||
code: params.code, | ||
redirect_uri: this.redirectUri, | ||
redirect_uri: params.redirectUri, | ||
client_id: this.client.settings.clientId, | ||
code_verifier: this.codeVerifier, | ||
code_verifier: params.codeVerifier, | ||
}; | ||
@@ -105,0 +113,0 @@ return tokenResponseToOAuth2Token(this.client.request('tokenEndpoint', body)); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
145717
2254
393