Skip to content

Commit c68ceaa

Browse files
committed
add request support for code flow w/ pkce
1 parent fd06a3e commit c68ceaa

File tree

4 files changed

+79
-2
lines changed

4 files changed

+79
-2
lines changed

src/SigninRequest.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ export class SigninRequest {
3939
}
4040

4141
let oidc = SigninRequest.isOidc(response_type);
42-
this.state = new SigninState({ nonce: oidc, data, client_id, authority });
42+
let code = SigninRequest.isCode(response_type);
43+
this.state = new SigninState({ nonce: oidc, data, client_id, authority, code_verifier: code });
4344

4445
url = UrlUtility.addQueryParam(url, "client_id", client_id);
4546
url = UrlUtility.addQueryParam(url, "redirect_uri", redirect_uri);
@@ -50,6 +51,11 @@ export class SigninRequest {
5051
if (oidc) {
5152
url = UrlUtility.addQueryParam(url, "nonce", this.state.nonce);
5253
}
54+
if (code) {
55+
url = UrlUtility.addQueryParam(url, "code_challenge", this.state.code_challenge);
56+
url = UrlUtility.addQueryParam(url, "code_challenge_method", "S256");
57+
url = UrlUtility.addQueryParam(url, "response_mode", "fragment");
58+
}
5359

5460
var optional = { prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values, resource, request, request_uri };
5561
for(let key in optional){
@@ -78,4 +84,11 @@ export class SigninRequest {
7884
});
7985
return !!(result[0]);
8086
}
87+
88+
static isCode(response_type) {
89+
var result = response_type.split(/\s+/g).filter(function(item) {
90+
return item === "code";
91+
});
92+
return !!(result[0]);
93+
}
8194
}

src/SigninState.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33

44
import { Log } from './Log';
55
import { State } from './State';
6+
import { JoseUtil } from './JoseUtil';
67
import random from './random';
78

89
export class SigninState extends State {
9-
constructor({nonce, authority, client_id} = {}) {
10+
constructor({nonce, authority, client_id, code_verifier} = {}) {
1011
super(arguments[0]);
1112

1213
if (nonce === true) {
@@ -16,6 +17,17 @@ export class SigninState extends State {
1617
this._nonce = nonce;
1718
}
1819

20+
if (code_verifier === true) {
21+
this._code_verifier = random();
22+
}
23+
else if (code_verifier) {
24+
this._code_verifier = code_verifier;
25+
}
26+
27+
if (this.code_verifier) {
28+
this._code_challenge = JoseUtil.hashString(this.code_verifier, "SHA256");
29+
}
30+
1931
this._authority = authority;
2032
this._client_id = client_id;
2133
}
@@ -29,6 +41,12 @@ export class SigninState extends State {
2941
get client_id() {
3042
return this._client_id;
3143
}
44+
get code_verifier() {
45+
return this._code_verifier;
46+
}
47+
get code_challenge() {
48+
return this._code_challenge;
49+
}
3250

3351
toStorageString() {
3452
Log.debug("SigninState.toStorageString");
@@ -37,6 +55,7 @@ export class SigninState extends State {
3755
data: this.data,
3856
created: this.created,
3957
nonce: this.nonce,
58+
code_verifier: this.code_verifier,
4059
authority: this.authority,
4160
client_id: this.client_id
4261
});

test/unit/SigninRequest.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ describe("SigninRequest", function() {
196196
subject.url.should.contain('hd=domain.com&foo=bar');
197197
});
198198

199+
it("should include code flow params", function() {
200+
settings.response_type = "code";
201+
subject = new SigninRequest(settings);
202+
subject.url.should.contain("code_challenge=");
203+
subject.url.should.contain("code_challenge_method=S256");
204+
subject.url.should.contain("response_mode=fragment");
205+
});
206+
207+
it("should include hybrid flow params", function() {
208+
settings.response_type = "code id_token";
209+
subject = new SigninRequest(settings);
210+
subject.url.should.contain("nonce=");
211+
subject.url.should.contain("code_challenge=");
212+
subject.url.should.contain("code_challenge_method=S256");
213+
subject.url.should.contain("response_mode=fragment");
214+
});
199215
});
200216

201217
describe("isOidc", function() {
@@ -216,4 +232,18 @@ describe("SigninRequest", function() {
216232
});
217233
});
218234

235+
describe("isCode", function() {
236+
it("should indicate if response_type is code", function() {
237+
SigninRequest.isCode("code").should.be.true;
238+
SigninRequest.isCode("id_token code").should.be.true;
239+
SigninRequest.isCode("code id_token").should.be.true;
240+
SigninRequest.isCode("id_token token code").should.be.true;
241+
SigninRequest.isCode("id_token code token").should.be.true;
242+
SigninRequest.isCode("code id_token token").should.be.true;
243+
244+
SigninRequest.isCode("id_token token").should.be.false;
245+
SigninRequest.isCode("token id_token").should.be.false;
246+
});
247+
});
248+
219249
});

test/unit/SigninState.spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,21 @@ describe("SigninState", function() {
3434
subject.nonce.should.be.ok;
3535
});
3636

37+
it("should accept code_verifier", function() {
38+
var subject = new SigninState({ code_verifier: 5 });
39+
subject.code_verifier.should.be.equal(5);
40+
});
41+
42+
it("should generate code_verifier", function() {
43+
var subject = new SigninState({ code_verifier: true });
44+
subject.code_verifier.should.be.ok;
45+
});
46+
47+
it("should generate code_challenge", function() {
48+
var subject = new SigninState({ code_verifier: true });
49+
subject.code_challenge.should.be.ok;
50+
});
51+
3752
it("should accept client_id", function() {
3853
var subject = new SigninState({ client_id: "client" });
3954
subject.client_id.should.be.equal("client");

0 commit comments

Comments
 (0)