Skip to content

Commit

Permalink
WIP: add google sign in redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
AJ ONeal committed Jan 2, 2022
1 parent fd37c51 commit 4656d7b
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 17 deletions.
5 changes: 4 additions & 1 deletion example/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,10 @@ async function getUserByPassword(req) {
//secret: process.env.HMAC_SECRET || process.env.COOKIE_SECRET,
});
sessionMiddleware.oidc({
"accounts.google.com": { clientId: process.env.GOOGLE_CLIENT_ID },
"accounts.google.com": {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
});
sessionMiddleware.challenge({
notify: notify,
Expand Down
13 changes: 13 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,18 @@ function SUSPICIOUS_TOKEN() {

// Likely Developer Mistakes

/**
* @param {any} [details]
* @returns {AuthError}
*/
function OIDC_BAD_GATEWAY(details) {
return create("remote server gave a non-OK response", {
status: 502,
code: "E_OIDC_BAD_GATEWAY",
details: details,
});
}

/**
* @param {any} [details]
* @returns {AuthError}
Expand Down Expand Up @@ -192,6 +204,7 @@ module.exports = {
SUSPICIOUS_REQUEST,
SUSPICIOUS_TOKEN,
SESSION_INVALID,
OIDC_BAD_GATEWAY,
// Dev
DEVELOPER_ERROR,
WRONG_TOKEN_TYPE,
Expand Down
75 changes: 63 additions & 12 deletions lib/oidc/accounts.google.com/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ var E = require("../../errors.js");
var OIDC = require("../");
var crypto = require("crypto");

// TODO initialize and require from ./lib/request.js
let request = require("@root/request");

function toUrlBase64(val) {
return Buffer.from(val)
.toString("base64")
Expand All @@ -22,6 +25,8 @@ function authorize({
grantTokensAndCookie,
verifyOidcToken,
}) {
let issuer = "https://accounts.google.com";

/**
* @param {string} clientId
* @param {OidcVerifyOpts} verifyOpts
Expand All @@ -30,7 +35,7 @@ function authorize({
if (!verifyOpts) {
verifyOpts = {};
}
verifyOpts.iss = "https://accounts.google.com";
verifyOpts.iss = issuer;
return verifyOidcToken(
verifyOpts,
/**
Expand All @@ -47,6 +52,11 @@ function authorize({
);
}

async function fetchOidcConfig(req, res, next) {
req.oidcConfig = await OIDC.getConfig(issuer);
next();
}

let google = opts.oidc["accounts.google.com"] || opts.oidc.google;
let googleVerifierOpts = {};
if (opts.DEVELOPMENT && !opts.__DEVELOPMENT_2) {
Expand Down Expand Up @@ -88,7 +98,6 @@ function authorize({
};

function redirectGoogleSignIn(clientId, loginUrl) {
var oidcBaseUrl = "https://accounts.google.com/o/oauth2/v2/auth";
let trustedUrl = loginUrl || opts.issuer;
if (!trustedUrl) {
throw Error("[auth3000] [google] no issuer / loginUrl given");
Expand All @@ -114,6 +123,8 @@ function authorize({
}

async function redirectToGoogle(req, res) {
//var oidcAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth";
var oidcAuthUrl = req.oidcConfig.authorization_endpoint;
let requestedRedirect = req.headers.referer || trustedUrl;

let isUnsafe = !prefixesUrl(trustedUrl, requestedRedirect);
Expand All @@ -125,18 +136,24 @@ function authorize({
return;
}

// TODO use base62 crc32
let state = {
u: requestedRedirect,
r: crypto.randomInt(Math.pow(2, 32) - 1).toString(16),
};
state = toUrlBase64(JSON.stringify(state));

let selfUrl = req.originalUrl || req.url;
selfUrl = new URL(`https://example.com${selfUrl}`).pathname;
selfUrl = `${opts.issuer}${selfUrl}`;

let url = OIDC.generateOidcUrl(
oidcBaseUrl,
oidcAuthUrl,
clientId,
state,
// ex: https://app.example.com/api/authn/session/oidc/accounts.google.com/redirect
`${opts.issuer}${req.url}`,
req.query.scope,
selfUrl,
state,
req.query.scope || "email profile",
req.query.login_hint
);

Expand All @@ -158,9 +175,14 @@ function authorize({
}

// don't pass these to the front end
req.query.state = undefined;
req.query.id_token = undefined;
req.query.access_token = undefined;
// TODO remove rather than set undefined
req.query.state = "";
if (req.query.id_token) {
req.query.id_token = "";
}
if (req.query.access_token) {
req.query.access_token = "";
}
// pass what google gave us (such as errors) to the front end
let search = new URLSearchParams(req.query).toString();

Expand Down Expand Up @@ -198,12 +220,41 @@ function authorize({
// (TODO: allow client-side redirect_uri)
app.get(
"/session/oidc/accounts.google.com/redirect",
function _upgradeGoogleResponse(req, res, next) {
fetchOidcConfig,
async function _upgradeGoogleResponse(req, res, next) {
var oidcTokenUrl = req.oidcConfig.token_endpoint;
// (2) a little monkey patch to switche Google's id_token query param
// to a Bearer, because that's what the existing token verifier expects
if (req.query.id_token) {
req.headers.authorization = `Bearer ${req.query.id_token}`;
if (!req.query.code) {
next();
return;
}

let clientId = google.clientId;
let clientSecret = google.clientSecret;
let code = req.query.code;

let selfUrl = req.originalUrl || req.url;
selfUrl = new URL(`https://example.com${selfUrl}`).pathname;
selfUrl = `${opts.issuer}${selfUrl}`;

let resp = await request({
method: "POST",
url: oidcTokenUrl,
// www-urlencoded...
json: true,
form: {
client_id: clientId,
client_secret: clientSecret,
code: code,
grant_type: "authorization_code",
redirect_uri: selfUrl,
},
});

let id_token = resp.toJSON().body.id_token;

req.headers.authorization = `Bearer ${id_token}`;
next();
},
verifyGoogleToken(
Expand Down
45 changes: 42 additions & 3 deletions lib/oidc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

let crypto = require("crypto");
let OIDC = module.exports;
let Errors = require("../errors.js");

let request = require("@root/request");

OIDC._querystringify = function (params) {
// { foo: 'bar', baz: 'qux' } => foo=bar&baz=qux
Expand All @@ -24,8 +27,8 @@ OIDC.generateOidcUrl = function (
client_id,
redirect_uri,
state,
scope,
login_hint
scope = "",
login_hint = ""
) {
// response_type=id_token requires a nonce (one-time use random value)
// response_type=token (access token) does not
Expand All @@ -34,5 +37,41 @@ OIDC.generateOidcUrl = function (
// transform from object to 'param1=escaped1&param2=escaped2...'
var params = OIDC._querystringify(options);

return `${oidcBaseUrl}?response_type=id_token&access_type=online&${params}`;
return `${oidcBaseUrl}?response_type=code&access_type=online&${params}`;
};

async function mustOk(resp) {
if (resp.statusCode >= 200 && resp.statusCode < 300) {
return resp;
}
throw Errors.OIDC_BAD_GATEWAY();
}

OIDC.getConfig = async function (issuer) {
// TODO check cache headers / cache for 5 minutes
let oidcUrl = issuer;
if (!oidcUrl.endsWith("/")) {
oidcUrl += "/";
}
oidcUrl += ".well-known/openid-configuration";

// See examples:
// Google: https://accounts.google.com/.well-known/openid-configuration
// Auth0: https://example.auth0.com/.well-known/openid-configuration
// Okta: https://login.writesharper.com/.well-known/openid-configuration
let resp = await request({ url: oidcUrl, json: true })
.then(mustOk)
.catch(function (err) {
console.error(`Could not get '${oidcUrl}':`);
console.error(err);
throw Errors.create(
"could not fetch OpenID Configuration - try inspecting the token and checking 'iss'",
{
code: "E_BAD_REMOTE",
status: 422,
}
);
});

return resp.body;
};
5 changes: 4 additions & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
<h3>Social Login</h3>
<div>
<span class="js-social-login" hidden>
<a class="js-google-oidc-url">Google Sign In</a>
<a style="display: none" class="js-google-oidc-url">Google Sign In</a>
<a href="/api/authn/session/oidc/accounts.google.com/redirect"
>Google Sign In</a
>
|
<a class="js-github-oauth2-url">GitHub Sign In</a>
</span>
Expand Down

0 comments on commit 4656d7b

Please sign in to comment.