Skip to content

Commit

Permalink
Merge pull request AzureAD#2656 from AzureAD/device-code-timeout
Browse files Browse the repository at this point in the history
Adding request timeout for device code flow
  • Loading branch information
samuelkubai authored Dec 17, 2020
2 parents ed9ce54 + 2b3c495 commit 02a34c0
Show file tree
Hide file tree
Showing 13 changed files with 17,242 additions and 145 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "@azure/msal-angular",
"email": "[email protected]",
"dependentChangeType": "none",
"date": "2020-11-25T16:26:49.257Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "@azure/msal-angularjs",
"email": "[email protected]",
"dependentChangeType": "none",
"date": "2020-11-25T16:27:01.359Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "@azure/msal-browser",
"email": "[email protected]",
"dependentChangeType": "none",
"date": "2020-11-25T16:27:16.921Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "patch",
"comment": "Adding device code timeout to the device code request(#2656)",
"packageName": "@azure/msal-common",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-11-25T16:28:12.416Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "@azure/msal-node",
"email": "[email protected]",
"dependentChangeType": "patch",
"date": "2020-11-25T16:29:04.442Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "@azure/msal-react",
"email": "[email protected]",
"dependentChangeType": "none",
"date": "2020-11-25T16:29:14.020Z"
}
8 changes: 8 additions & 0 deletions change/msal-2020-11-25-19-29-14-device-code-timeout.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "none",
"comment": "package.lock change",
"packageName": "msal",
"email": "[email protected]",
"dependentChangeType": "none",
"date": "2020-11-25T16:28:26.323Z"
}
12 changes: 12 additions & 0 deletions lib/msal-common/src/client/DeviceCodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export class DeviceCodeClient extends BaseClient {
const requestBody = this.createTokenRequestBody(request, deviceCodeResponse);
const headers: Record<string, string> = this.createDefaultTokenRequestHeaders();

const userSpecifiedTimeout = request.timeout ? TimeUtils.nowSeconds() + request.timeout : undefined;
const deviceCodeExpirationTime = TimeUtils.nowSeconds() + deviceCodeResponse.expiresIn;
const pollingIntervalMilli = deviceCodeResponse.interval * 1000;

Expand All @@ -160,7 +161,18 @@ export class DeviceCodeClient extends BaseClient {
clearInterval(intervalId);
reject(ClientAuthError.createDeviceCodeCancelledError());

} else if (userSpecifiedTimeout && userSpecifiedTimeout < deviceCodeExpirationTime && TimeUtils.nowSeconds() > userSpecifiedTimeout) {

this.logger.error(`User defined timeout for device code polling reached. The timeout was set for ${userSpecifiedTimeout}`);
clearInterval(intervalId);
reject(ClientAuthError.createUserTimeoutReachedError());

} else if (TimeUtils.nowSeconds() > deviceCodeExpirationTime) {

if (userSpecifiedTimeout) {
this.logger.verbose(`User specified timeout ignored as the device code has expired before the timeout elapsed. The user specified timeout was set for ${userSpecifiedTimeout}`);
}

this.logger.error(`Device code expired. Expiration time of device code was ${deviceCodeExpirationTime}`);
clearInterval(intervalId);
reject(ClientAuthError.createDeviceCodeExpiredError());
Expand Down
11 changes: 11 additions & 0 deletions lib/msal-common/src/error/ClientAuthError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ export const ClientAuthErrorMessage = {
code: "token_refresh_required",
desc: "Cannot return token from cache because it must be refreshed. This may be due to one of the following reasons: forceRefresh parameter is set to true, claims have been requested, there is no cached access token or it is expired."
},
userTimeoutReached: {
code: "user_timeout_reached",
desc: "User defined timeout for device code polling reached",
},
tokenClaimsRequired: {
code: "token_claims_cnf_required_for_signedjwt",
desc: "Cannot generate a POP jwt if the token_claims are not populated"
Expand Down Expand Up @@ -439,6 +443,13 @@ export class ClientAuthError extends AuthError {
}

/**
* Throws error if the user defined timeout is reached.
*/
static createUserTimeoutReachedError(): ClientAuthError {
return new ClientAuthError(ClientAuthErrorMessage.userTimeoutReached.code, ClientAuthErrorMessage.userTimeoutReached.desc);
}

/*
* Throws error if token claims are not populated for a signed jwt generation
*/
static createTokenClaimsRequiredError(): ClientAuthError {
Expand Down
2 changes: 2 additions & 0 deletions lib/msal-common/src/request/DeviceCodeRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import { BaseAuthRequest } from "./BaseAuthRequest";
* - cancel - Boolean to cancel polling of device code endpoint. While the user authenticates on a separate device, MSAL polls the the token endpoint of security token service for the interval specified in the device code response (usually 15 minutes). To stop polling and cancel the request, set cancel=true.
* - resourceRequestMethod - HTTP Request type used to request data from the resource (i.e. "GET", "POST", etc.). Used for proof-of-possession flows.
* - resourceRequestUri - URI that token will be used for. Used for proof-of-possession flows.
* - timeout - Period in which the user explicitly configures for the polling of the device code endpoint. At the end of this period; assuming the device code has not expired yet; the device code polling is stopped and the request cancelled. The device code expiration window will always take precedence over this set period.
*/
export type DeviceCodeRequest = BaseAuthRequest & {
deviceCodeCallback: (response: DeviceCodeResponse) => void;
cancel?: boolean;
timeout?: number;
};
18 changes: 18 additions & 0 deletions lib/msal-common/test/client/DeviceCodeClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,23 @@ describe("DeviceCodeClient unit tests", async () => {
const client = new DeviceCodeClient(config);
await expect(client.acquireToken(request)).to.be.rejectedWith(`${ClientAuthErrorMessage.DeviceCodeExpired.desc}`);
}).timeout(6000);

it("Throw device code expired exception if the timeout expires", async () => {
sinon.stub(DeviceCodeClient.prototype, <any>"executePostRequestToDeviceCodeEndpoint").resolves(DEVICE_CODE_RESPONSE);
const tokenRequestStub = sinon
.stub(BaseClient.prototype, <any>"executePostToTokenEndpoint")
.onFirstCall().resolves(AUTHORIZATION_PENDING_RESPONSE)

let deviceCodeResponse = null;
const request: DeviceCodeRequest = {
scopes: [...TEST_CONFIG.DEFAULT_GRAPH_SCOPE, ...TEST_CONFIG.DEFAULT_SCOPES],
deviceCodeCallback: (response) => deviceCodeResponse = response,
timeout: DEVICE_CODE_RESPONSE.interval, // Setting a timeout equal to the interval polling time to allow for one call to the token endpoint
};

const client = new DeviceCodeClient(config);
await expect(client.acquireToken(request)).to.be.rejectedWith(`${ClientAuthErrorMessage.userTimeoutReached.desc}`);
await expect(tokenRequestStub.callCount).to.equal(1);
}).timeout(15000);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var msal = require('@azure/msal-node');
const msalConfig = {
auth: {
clientId: "6c04f413-f6e7-4690-b372-dbdd083e7e5a",
authority: "https://login.microsoftonline.com/sgonz.onmicrosoft.com",
authority: "https://login.microsoftonline.com/sgonz.onmicrosoft.com",
}
};

Expand All @@ -17,6 +17,7 @@ const pca = new msal.PublicClientApplication(msalConfig);
const deviceCodeRequest = {
deviceCodeCallback: (response) => (console.log(response.message)),
scopes: ["user.read"],
timeout: 5,
};

pca.acquireTokenByDeviceCode(deviceCodeRequest).then((response) => {
Expand Down
Loading

0 comments on commit 02a34c0

Please sign in to comment.