Skip to content

Commit

Permalink
Add support for sending client_secret with authorization_code
Browse files Browse the repository at this point in the history
Also added a bunch of tests for this.

Fixes #70
  • Loading branch information
evert committed Jul 4, 2022
1 parent 9319705 commit 86ecbb3
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 14 deletions.
6 changes: 6 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

2.0.15 (2022-??-??)
-------------------

* #70: Allow send Client Secret with `authorization_code`


2.0.14 (2022-06-23)
-------------------

Expand Down
4 changes: 3 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,11 @@ export class OAuth2Client {
'Content-Type': 'application/x-www-form-urlencoded',
};

if (body.grant_type !== 'authorization_code' && this.settings.clientSecret) {
if (this.settings.clientSecret) {
const basicAuthStr = btoa(this.settings.clientId + ':' + this.settings.clientSecret);
headers.Authorization = 'Basic ' + basicAuthStr;
} else if (body.grant_type === 'authorization_code') {
body.client_id = this.settings.clientId;
}

const resp = await fetch(uri, {
Expand Down
5 changes: 2 additions & 3 deletions src/client/authorization-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export class OAuth2AuthorizationCodeClient {
]);

const query: AuthorizationQueryParams = {
response_type: 'code',
client_id: this.client.settings.clientId,
response_type: 'code',
redirect_uri: params.redirectUri,
code_challenge_method: codeChallenge?.[0],
code_challenge: codeChallenge?.[1],
Expand Down Expand Up @@ -138,7 +138,6 @@ export class OAuth2AuthorizationCodeClient {
grant_type: 'authorization_code',
code: params.code,
redirect_uri: params.redirectUri,
client_id: this.client.settings.clientId,
code_verifier: params.codeVerifier,
};
return tokenResponseToOAuth2Token(this.client.request('tokenEndpoint', body));
Expand All @@ -150,7 +149,7 @@ export class OAuth2AuthorizationCodeClient {

export async function generateCodeVerifier(): Promise<string> {

if (typeof window !== 'undefined' && window.crypto) {
if ((typeof window !== 'undefined' && window.crypto) || (typeof self !== 'undefined' && self.crypto)) {
// Built-in webcrypto
const arr = new Uint8Array(32);
crypto.getRandomValues(arr);
Expand Down
1 change: 0 additions & 1 deletion src/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export type AuthorizationCodeRequest = {
grant_type: 'authorization_code';
code: string;
redirect_uri: string;
client_id: string;
code_verifier: string|undefined;
}

Expand Down
75 changes: 75 additions & 0 deletions test/authorization-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { testServer } from './test-server';
import { OAuth2Client } from '../src';
import { expect } from 'chai';

describe('authorization-code', () => {

it('should send requests to the token endpoint', async() => {

const server = testServer();

const client = new OAuth2Client({
server: server.url,
tokenEndpoint: '/token',
clientId: 'test-client-id',
});

const result = await client.authorizationCode.getToken({
code: 'code_000',
redirectUri: 'http://example/redirect',
});

expect(result.accessToken).to.equal('access_token_000');
expect(result.refreshToken).to.equal('refresh_token_000');
expect(result.expiresAt).to.be.lessThanOrEqual(Date.now() + 3600_000);
expect(result.expiresAt).to.be.greaterThanOrEqual(Date.now() + 3500_000);

server.close();

const request = server.lastRequest();
expect(request.headers.get('Authorization')).to.equal(null);

expect(request.body).to.eql({
client_id: 'test-client-id',
grant_type: 'authorization_code',
code: 'code_000',
redirect_uri: 'http://example/redirect',
});

});

it('should send client_id and client_secret in the Authorization header if secret was specified', async() => {

const server = testServer();

const client = new OAuth2Client({
server: server.url,
tokenEndpoint: '/token',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
});

const result = await client.authorizationCode.getToken({
code: 'code_000',
redirectUri: 'http://example/redirect',
});

expect(result.accessToken).to.equal('access_token_000');
expect(result.refreshToken).to.equal('refresh_token_000');
expect(result.expiresAt).to.be.lessThanOrEqual(Date.now() + 3600_000);
expect(result.expiresAt).to.be.greaterThanOrEqual(Date.now() + 3500_000);

server.close();

const request = server.lastRequest();
expect(request.headers.get('Authorization')).to.equal('Basic ' + btoa('test-client-id:test-client-secret'));

expect(request.body).to.eql({
grant_type: 'authorization_code',
code: 'code_000',
redirect_uri: 'http://example/redirect',
});

});

});
10 changes: 7 additions & 3 deletions test/client-credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ describe('client-credentials', () => {
const server = testServer();

const client = new OAuth2Client({
server: 'http://localhost:44444',
tokenEndpoint: '/token/client-credentials',
server: server.url,
tokenEndpoint: '/token',
clientId: 'test-client-id',
clientSecret: 'test-client-secret',
});
Expand All @@ -22,8 +22,12 @@ describe('client-credentials', () => {
expect(result.expiresAt).to.be.lessThanOrEqual(Date.now() + 3600_000);
expect(result.expiresAt).to.be.greaterThanOrEqual(Date.now() + 3500_000);

server.close();
const request = server.lastRequest();
expect(request.headers.get('Authorization')).to.equal('Basic ' + btoa('test-client-id:test-client-secret'));

expect(request.body).to.eql({
grant_type: 'client_credentials',
});
});

});
37 changes: 31 additions & 6 deletions test/test-server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { Application, Middleware, Request } from '@curveball/core';
import bodyParser from '@curveball/bodyparser';
import * as http from 'http';

type TestServer = {
server: http.Server;
app: Application;
lastRequest: () => Request;
port: number;
url: string;
close: () => Promise<void>;
}

let serverCache: null|TestServer = null;

export function testServer() {

if (serverCache) return serverCache;

let lastRequest: any = null;

const app = new Application();
Expand All @@ -12,23 +26,34 @@ export function testServer() {
lastRequest = ctx.request;
return next();
});
app.use(clientCredentials);
const server = app.listen(44444);
app.use(issueToken);
const port = 40000 + Math.round(Math.random()*9999);
const server = app.listen(port);

return {
serverCache = {
server,
app,
lastRequest: (): Request => lastRequest,
close: () => server.close()
port,
url: 'http://localhost:' + port,
close: async() => {

return new Promise<void>(res => {
server.close(() => res());
});

}

};
return serverCache;

}



const clientCredentials: Middleware = (ctx, next) => {
const issueToken: Middleware = (ctx, next) => {

if (ctx.path !== '/token/client-credentials') {
if (ctx.path !== '/token') {
return next();
}

Expand Down

0 comments on commit 86ecbb3

Please sign in to comment.