Skip to content

Commit

Permalink
Authentication: Move to first party app and change flow to implicit f…
Browse files Browse the repository at this point in the history
…low.
  • Loading branch information
carlosscastro committed Dec 3, 2018
1 parent 4682165 commit cf1b9bc
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 48 deletions.
2 changes: 1 addition & 1 deletion packages/app/main/src/commands/azureCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function registerCommands(commandRegistry: CommandRegistry) {
commandRegistry.registerCommand(Azure.RetrieveArmToken, async (renew: boolean = false) => {
const settingsStore = getSettingsStore();
const serverUrl = (emulator.framework.serverUrl || '').replace('[::]', 'localhost');
const workflow = AzureAuthWorkflowService.retrieveAuthToken(renew, `${serverUrl}/v4/token`);
const workflow = AzureAuthWorkflowService.retrieveAuthToken(renew);
let result = undefined;
while (true) {
const next = workflow.next(result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jest.mock('electron', () => ({
BrowserWindow: class MockBrowserWindow {
public static reporters = [];
public listeners = [] as any;
public webContents = { history: ['http://someotherUrl', `http://localhost/#t=13&id_token=${mockArmToken}`] };
public webContents = { history: ['http://someotherUrl', `http://localhost/#t=13&access_token=${mockArmToken}`] };

private static report(...args: any[]) {
this.reporters.forEach(r => r(args));
Expand All @@ -53,7 +53,7 @@ jest.mock('electron', () => ({
if (type === 'page-title-updated') {
[['http://someotherUrl'], [`http://localhost/#t=13&id_token=${mockArmToken}`]].forEach((url, index) => {
let evt = new mockEvent('page-title-updated');
(evt as any).sender = { history: [`http://localhost/#t=13&id_token=${mockArmToken}`] };
(evt as any).sender = { history: [`http://localhost/#t=13&access_token=${mockArmToken}`] };
setTimeout(() => {
this.listeners.forEach(l => l.type === evt.type && l.handler(evt));
}, 25 * index);
Expand Down Expand Up @@ -103,7 +103,7 @@ describe('The azureAuthWorkflowService', () => {
let reportedValues = [];
let reporter = v => reportedValues.push(v);
(BrowserWindow as any).reporters.push(reporter);
const it = AzureAuthWorkflowService.retrieveAuthToken(false, '');
const it = AzureAuthWorkflowService.retrieveAuthToken(false);
let value = undefined;
let ct = 0;
while (true) {
Expand Down Expand Up @@ -131,7 +131,7 @@ describe('The azureAuthWorkflowService', () => {
}

if (ct === 1) {
expect(value.id_token).toBe(mockArmToken);
expect(value.access_token).toBe(mockArmToken);
// Not sure if this is valuable or not.
expect(reportedValues.length).toBe(3);
}
Expand Down
66 changes: 23 additions & 43 deletions packages/app/main/src/services/azureAuthWorkflowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,37 @@ import uuidv4 from 'uuid/v4';
import * as jwt from 'jsonwebtoken';

let getPem = require('rsa-pem-from-mod-exp');
const clientId = '4f28e5eb-6b7f-49e6-ac0e-f992b622da57';

const clientId = 'f3723d34-6ff5-4ceb-a148-d99dcd2511fc';
const replyUrl = 'https://dev.botframework.com/cb';
const authorizationEndpoint = 'https://login.microsoftonline.com/common/oauth2/authorize';

declare type Config = { authorization_endpoint: string, jwks_uri: string, token_endpoint: string };
declare type AuthResponse = { code: string, id_token: string, state: string, session_state: string, error?: string };
declare type AuthResponse = {
code: string, access_token: string, state: string, session_state: string, error?: string };
declare type Jwks = { keys: { x5t: string, n: string, e: string, x5c: string[] }[] };

export class AzureAuthWorkflowService {

private static config: Config;
private static jwks: Jwks;

public static* retrieveAuthToken(renew: boolean = false, redirectUri: string): IterableIterator<any> {
const authWindow = yield this.launchAuthWindow(renew, redirectUri);
public static* retrieveAuthToken(renew: boolean = false): IterableIterator<any> {
const authWindow = yield this.launchAuthWindow(renew);
authWindow.show();
const result = yield this.waitForAuthResult(authWindow, redirectUri);
const result = yield this.waitForAuthResult(authWindow, replyUrl);
authWindow.close();
if (result.error) {
return false;
}
const armToken = yield this.getArmTokenFromCode(result.code, redirectUri);
if (armToken.error) {
const valid = yield this.validateJWT(result.access_token);
if (!valid) {
result.error = 'Invalid Token';
}
if (result.error) {
return false;
}
yield armToken;
yield result;
}

private static async waitForAuthResult(browserWindow: BrowserWindow, redirectUri: string): Promise<AuthResponse> {
Expand Down Expand Up @@ -97,14 +105,14 @@ export class AzureAuthWorkflowService {
return response;
}

const isValid = await this.validateJWT(response.id_token);
const isValid = await this.validateJWT(response.access_token);
if (!isValid) {
response.error = 'Invalid token';
}
return response;
}

private static async launchAuthWindow(renew: boolean, redirectUri: string): Promise<BrowserWindow> {
private static async launchAuthWindow(renew: boolean): Promise<BrowserWindow> {
const browserWindow = new BrowserWindow({
modal: true,
show: false,
Expand All @@ -115,20 +123,20 @@ export class AzureAuthWorkflowService {
height: 366,
webPreferences: { contextIsolation: true, nativeWindowOpen: true }
});

browserWindow.setMenu(null);
const { authorization_endpoint: endpoint } = await this.getConfig();

const state = uuidv4();
const requestId = uuidv4();
const nonce = uuidv4();
const bits = [
`${endpoint}?response_type=id_token+code`,
`${authorizationEndpoint}?response_type=token`,
`client_id=${clientId}`,
`redirect_uri=${redirectUri}`,
`redirect_uri=${replyUrl}}`,
`state=${state}`,
`client-request-id=${requestId}`,
`nonce=${nonce}`,
'x-client-SKU=Js',
'x-client-Ver=1.0.17',
'response_mode=fragment',
'resource=https://management.core.windows.net/'
];

Expand Down Expand Up @@ -163,34 +171,6 @@ export class AzureAuthWorkflowService {
return this.jwks;
}

private static async getArmTokenFromCode(code: string, redirectUri: string): Promise<any> {
const { token_endpoint: endpoint } = await this.getConfig();
const bits = [
`grant_type=authorization_code`,
`client_id=${clientId}`,
`code=${code}`,
`redirect_uri=${redirectUri}`,
`resource=https://management.core.windows.net/`
];
const data = bits.join('&');
let armToken: { access_token: string, refresh_token: string, error?: string };
try {
const response = await fetch(endpoint, {
body: data,
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
armToken = await response.json();
const valid = await this.validateJWT(armToken.access_token);
if (!valid) {
armToken.error = 'Invalid Token';
}
} catch (e) {
armToken.error = e.toString();
}
return armToken;
}

private static async validateJWT(token: string): Promise<boolean> {
const [header] = token.split('.');
const headers: { alg: string, kid: string, x5t: string } = JSON.parse(Buffer.from(header, 'base64').toString());
Expand Down

0 comments on commit cf1b9bc

Please sign in to comment.