* Exchange code. * * @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3
({
grant_type = "authorization_code",
redirect_uri = this._settings.redirect_uri,
client_id = this._settings.client_id,
client_secret = this._settings.client_secret,
extraHeaders,
...args
}: ExchangeCodeArgs)
| 85 | * @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.3 |
| 86 | */ |
| 87 | public async exchangeCode({ |
| 88 | grant_type = "authorization_code", |
| 89 | redirect_uri = this._settings.redirect_uri, |
| 90 | client_id = this._settings.client_id, |
| 91 | client_secret = this._settings.client_secret, |
| 92 | extraHeaders, |
| 93 | ...args |
| 94 | }: ExchangeCodeArgs): Promise<Record<string, unknown>> { |
| 95 | const logger = this._logger.create("exchangeCode"); |
| 96 | if (!client_id) { |
| 97 | logger.throw(new Error("A client_id is required")); |
| 98 | } |
| 99 | if (!redirect_uri) { |
| 100 | logger.throw(new Error("A redirect_uri is required")); |
| 101 | } |
| 102 | if (!args.code) { |
| 103 | logger.throw(new Error("A code is required")); |
| 104 | } |
| 105 | |
| 106 | const params = new URLSearchParams({ grant_type, redirect_uri }); |
| 107 | for (const [key, value] of Object.entries(args)) { |
| 108 | if (value != null) { |
| 109 | params.set(key, value); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | // Validate client_secret if required before making any external calls |
| 114 | if ((this._settings.client_authentication === "client_secret_basic" || |
| 115 | this._settings.client_authentication === "client_secret_jwt") && |
| 116 | (client_secret === undefined || client_secret === null)) { |
| 117 | logger.throw(new Error("A client_secret is required")); |
| 118 | // eslint-disable-next-line @typescript-eslint/only-throw-error |
| 119 | throw null; // https://github.com/microsoft/TypeScript/issues/46972 |
| 120 | } |
| 121 | |
| 122 | let basicAuth: string | undefined; |
| 123 | const url = await this._metadataService.getTokenEndpoint(false); |
| 124 | switch (this._settings.client_authentication) { |
| 125 | case "client_secret_basic": |
| 126 | basicAuth = CryptoUtils.generateBasicAuth(client_id, client_secret!); |
| 127 | break; |
| 128 | case "client_secret_post": |
| 129 | params.append("client_id", client_id); |
| 130 | if (client_secret) { |
| 131 | params.append("client_secret", client_secret); |
| 132 | } |
| 133 | break; |
| 134 | case "client_secret_jwt": { |
| 135 | const clientAssertion = await CryptoUtils.generateClientAssertionJwt(client_id, client_secret!, url, this._settings.token_endpoint_auth_signing_alg); |
| 136 | params.append("client_id", client_id); |
| 137 | params.append("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); |
| 138 | params.append("client_assertion", clientAssertion); |
| 139 | break; |
| 140 | } |
| 141 | } |
| 142 | logger.debug("got token endpoint"); |
| 143 | |
| 144 | const response = await this._jsonService.postForm(url, { |
no test coverage detected