-
-
Save adeperio/73ce6680d4b80b45e624ab62bacfbdca to your computer and use it in GitHub Desktop.
//@flow | |
import request from 'request' | |
import crypto from 'crypto' | |
import rp from 'request-promise' | |
export type AuthServiceConfig = { | |
authorizeEndpoint: string, | |
clientId: string, | |
audience: string, | |
scope: string, | |
redirectUri: string, | |
tokenEndpoint: string | |
} | |
//https://auth0.com/docs/api-auth/tutorials/authorization-code-grant-pkce | |
export default class AuthService { | |
challengePair : { verifier: string, challenge: string } | |
config: AuthServiceConfig | |
constructor(config: AuthServiceConfig){ | |
this.config = config | |
} | |
requestAuthCode() : string { | |
this.challengePair = AuthService.getPKCEChallengePair() | |
return this.getAuthoriseUrl(this.challengePair) | |
} | |
requestAccessCode(callbackUrl: string): Promise<any> { | |
return new Promise((resolve, reject) => { | |
if(this.isValidAccessCodeCallBackUrl(callbackUrl)) { | |
let authCode = AuthService.getParameterByName('code', callbackUrl) | |
if(authCode != null){ | |
let verifier = this.challengePair.verifier | |
let options = this.getTokenPostRequest(authCode, verifier) | |
return rp(options) | |
.then(function(response) { | |
//TODO: return / store access code, | |
//remove console.log, meant for demonstration purposes only | |
console.log('access token.response: ' + JSON.stringify(response)); | |
}) | |
.catch(function (err) { | |
if (err) throw new Error(err); | |
}); | |
} else { | |
reject('Could not parse the authorization code') | |
} | |
} else { | |
reject('Access code callback url not expected.') | |
} | |
}) | |
} | |
getAuthoriseUrl(challengePair: { verifier: string, challenge: string }) : string { | |
return `${this.config.authorizeEndpoint}?audience=${this.config.audience}&scope=${this.config.scope}&response_type=code&client_id=${this.config.clientId}&code_challenge=${challengePair.challenge}&code_challenge_method=S256&redirect_uri=${this.config.redirectUri}` | |
} | |
getTokenPostRequest(authCode: string, verifier: string){ | |
return { | |
method: 'POST', | |
url: this.config.tokenEndpoint, | |
headers: { 'content-type': 'application/json' }, | |
body: `{"grant_type":"authorization_code", | |
"client_id": "${this.config.clientId}", | |
"code_verifier": "${verifier}", | |
"code": "${authCode}", | |
"redirect_uri":"${this.config.redirectUri}" | |
}` | |
}; | |
} | |
isValidAccessCodeCallBackUrl(callbackUrl: string) : boolean { | |
return callbackUrl.indexOf(this.config.redirectUri) > -1 | |
} | |
static getPKCEChallengePair() : { verifier: string, challenge: string } { | |
let verifier = AuthService.base64URLEncode(crypto.randomBytes(32)); | |
let challenge = AuthService.base64URLEncode(AuthService.sha256(verifier)); | |
return { verifier, challenge }; | |
} | |
static getParameterByName(name: string, url: string) : ?string { | |
name = name.replace(/[\[\]]/g, "\\$&"); | |
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), | |
results = regex.exec(url); | |
if (!results) return null; | |
if (!results[2]) return ''; | |
return decodeURIComponent(results[2].replace(/\+/g, " ")); | |
} | |
static base64URLEncode(str: Buffer) : string { | |
return str.toString('base64') | |
.replace(/\+/g, '-') | |
.replace(/\//g, '_') | |
.replace(/=/g, ''); | |
} | |
static sha256(buffer: string) : Buffer { | |
return crypto.createHash('sha256').update(buffer).digest(); | |
} | |
} |
import path from "path"; | |
import events from 'events' | |
import { app, BrowserWindow } from "electron"; | |
import AuthService, { AuthServiceConfig } from "./AuthService" | |
function getAuthConfig(){ | |
//sample values - plug your Auth0 config here | |
var authConfig : AuthServiceConfig = { | |
clientId: 'rlasjf82130948asdkfjaslsaklaskfd', | |
authorizeEndpoint: 'https://myapp.auth0.com/authorize', | |
audience: 'https://myapi.com:8080', | |
scope: 'email%20given_name%20profile', | |
redirectUri: 'https://myapp.auth0.com/mobile', | |
tokenEndpoint: 'https://myapp.auth0.com/oauth/token' | |
} | |
return authConfig | |
} | |
app.on("ready", () => { | |
let authService = new AuthService(getAuthConfig()) | |
let authWindow = new BrowserWindow({ width: 800, height: 600 }) | |
/* | |
Go to hosted login page at the authorise endpoint | |
authenticate | |
and request auth code, and send challenge | |
*/ | |
authWindow.loadURL(authService.requestAuthCode()); | |
authWindow.webContents.on('did-get-redirect-request', function(event, oldUrl, newUrl) { | |
/* | |
after successfuly authenticating | |
get auth code from the redirect uri | |
and use that and the code verifier | |
to request an access code | |
*/ | |
authService.requestAccessCode(newUrl) | |
}); | |
}); | |
app.on('window-all-closed', () => { | |
// Respect the OSX convention of having the application in memory even | |
// after all windows have been closed | |
if (process.platform !== 'darwin') { | |
app.quit(); | |
} | |
}); | |
I've added my website as the api audience, not sure if I'll need to configure it on auth0 or not. One other note - launching electron still gives the same console error, and no authorize modal is presented, but the browser looks like it is being immediately redirected to the https://myapp.auth0.com/mobile link, I get an "OK" response in the window.
OK, I've got it working for my use case. Because I'm not implementing a custom API, and just need access to the user's info, I changed the config info as follows:
var authConfig : AuthServiceConfig = {
clientId: '...',
authorizeEndpoint: 'https://ultrasoundjelly.auth0.com/authorize',
audience: 'https://ultrasoundjelly.auth0.com/userinfo',
scope: 'openid',
redirectUri: 'https://ultrasoundjelly.auth0.com/mobile',
tokenEndpoint: 'https://ultrasoundjelly.auth0.com/oauth/token'
}
Now I'm getting the auth modal, successfully log in, and get a valid id_token echoed to the console to send to parse for user info. I am throwing a warning three times with this code, however:
Access code callback url not expected.
Aside from correcting this error. Now I'm trying to figure out how to 1) capture redirect event 2) show index.html 3) send my token to renderer.js. This code works for capturing the redirect:
authWindow.webContents.on('will-navigate', function(){
authWindow.close();
createmainWindow(); //this function creates my main app window and navigates to my index.html
});
No luck getting the token out of the promise. I've tried using localStorage.setItem and setting a global variable without luck. Any thoughts on returning the value to main.js so I can deal with it from there?
Actually, nix that last code. If I call authWindow.close()
on will-navigate I lose the promise response. I need to figure out how to hook into the promise from main.js.
Figured it out for anyone who needs help with this: I passed my createWindow
and authWindow
functions as variables in the authService.requestAccessCode(newUrl,createWindow)
function. The in the promise callback I passed the response and authWindow
as a variables for the createmainWindow(response, authWindow)
function. That gets me back to the main.js file where I can parse the response and call authWindow.close()
I've created a package that should handle both this and refresh token persistence. Check it out: https://github.com/jbreckmckye/electron-auth0-login
Qwerios,
Thanks 1E6 for posting this code. I'm trying to convert my lock auth0 electron app to a new auth protocol before auth0 stops supporting lock due to security concerns.
To implement your code I have done the following to my app:
Electron actually launches cleanly now, but I'm getting an error:
The error probably stems from 2 potential issues: 1) I cant figure out what to put in getAuthConfig for audience. Is this necessary? 2) Do I need to define a new auth0 client? I just used the old app's client. Any help here would be greatly appreciated. I'm surprised auth0 hasn't updated their electron integration quick-start code yet.
Ben