Skip to content

Commit 950df5b

Browse files
committed
Implement JWT Bearer grant
1 parent dfaa462 commit 950df5b

3 files changed

Lines changed: 74 additions & 3 deletions

File tree

src/client.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
IntrospectionRequest,
66
IntrospectionResponse,
77
PasswordRequest,
8+
JwtBearerRequest,
89
OAuth2TokenTypeHint,
910
RefreshRequest,
1011
RevocationRequest,
@@ -42,6 +43,15 @@ type PasswordParams = {
4243

4344
}
4445

46+
type JwtBearerParams = {
47+
/**
48+
* The JSON Web Token to use for the JWT Bearer token request.
49+
*/
50+
assertion: string;
51+
52+
scope?: string[];
53+
}
54+
4555
/**
4656
* Extra options that may be passed to refresh()
4757
*/
@@ -79,8 +89,8 @@ export interface ClientSettings {
7989
* OAuth2 clientSecret
8090
*
8191
* This is required when using the 'client_secret_basic' authenticationMethod
82-
* for the client_credentials and password flows, but not authorization_code
83-
* or implicit.
92+
* for the client_credentials and password flows, but not authorization_code,
93+
* implicit or JWT Bearer.
8494
*/
8595
clientSecret?: string;
8696

@@ -225,6 +235,19 @@ export class OAuth2Client {
225235

226236
}
227237

238+
/**
239+
* Retrieves an OAuth2 token using the 'urn:ietf:params:oauth:grant-type:jwt-bearer' grant.
240+
*/
241+
async jwtBearer(params: JwtBearerParams): Promise<OAuth2Token> {
242+
243+
const body: JwtBearerRequest = {
244+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
245+
assertion: params.assertion,
246+
scope: params.scope?.join(' '),
247+
};
248+
return this.tokenResponseToOAuth2Token(this.request('tokenEndpoint', body));
249+
}
250+
228251
/**
229252
* Returns the helper object for the `authorization_code` grant.
230253
*/
@@ -366,7 +389,7 @@ export class OAuth2Client {
366389
/**
367390
* Does a HTTP request on the 'token' endpoint.
368391
*/
369-
async request(endpoint: 'tokenEndpoint', body: RefreshRequest | ClientCredentialsRequest | PasswordRequest | AuthorizationCodeRequest): Promise<TokenResponse>;
392+
async request(endpoint: 'tokenEndpoint', body: RefreshRequest | ClientCredentialsRequest | PasswordRequest | JwtBearerRequest | AuthorizationCodeRequest): Promise<TokenResponse>;
370393
async request(endpoint: 'introspectionEndpoint', body: IntrospectionRequest): Promise<IntrospectionResponse>;
371394
async request(endpoint: 'revocationEndpoint', body: RevocationRequest): Promise<void>;
372395
async request(endpoint: OAuth2Endpoint, body: Record<string, any>): Promise<unknown> {

src/messages.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ export type PasswordRequest = {
5050
resource?: string | string[];
5151
}
5252

53+
/**
54+
* JWT Bearer Grant Type request body
55+
*/
56+
export type JwtBearerRequest = {
57+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer';
58+
assertion: string;
59+
scope?: string;
60+
}
61+
5362
export type AuthorizationCodeRequest = {
5463
grant_type: 'authorization_code';
5564
code: string;

test/jwt-bearer.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { testServer } from './test-server';
2+
import { OAuth2Client } from '../src';
3+
import { expect } from 'chai';
4+
5+
describe('jwt-bearer', () => {
6+
7+
it('should work with client_secret_post', async () => {
8+
9+
const server = testServer();
10+
11+
const client = new OAuth2Client({
12+
server: server.url,
13+
tokenEndpoint: '/token',
14+
clientId: 'test-client-id',
15+
});
16+
17+
const result = await client.jwtBearer({
18+
assertion: 'foobar',
19+
scope: ['hello', 'world']
20+
});
21+
22+
expect(result.accessToken).to.equal('access_token_000');
23+
expect(result.refreshToken).to.equal('refresh_token_000');
24+
expect(result.expiresAt).to.be.lessThanOrEqual(Date.now() + 3600_000);
25+
expect(result.expiresAt).to.be.greaterThanOrEqual(Date.now() + 3500_000);
26+
27+
const request = server.lastRequest();
28+
expect(request.headers.get('Authorization')).to.be.null;
29+
expect(request.headers.get('Accept')).to.equal('application/json');
30+
31+
expect(request.body).to.eql({
32+
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
33+
assertion: 'foobar',
34+
scope: 'hello world',
35+
client_id: 'test-client-id'
36+
});
37+
});
38+
39+
});

0 commit comments

Comments
 (0)