You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Responding to #305 (comment) as an issue so as not to derail that discussion.
"I have a complete cloud stack template (app + ci + deploy in cloud providers with oauth). There are other oauth systems, but for a flexible simple single oauth service vouch is reliable and simple. I use node.js instead of nginx as the router of requests (maybe that config could be useful to others, idk it feels a pretty rare case). In a sense it replaces using Auth0, Okta, etc, or an integrated OAuth library like http://www.passportjs.org. There's just so much complexity, possible vendor lock-in, expense, etc, that sometimes a tool solving a single task is preferable, at least in the beginning."
Architecture:
The auth service (can be a cloud-function/lambda or horizontally scaling pod) handles the redirection that nginx performs in vouch's documentation.
Here is my actual /login route handler, redirecting to vouch when needed. There's some of my app specific stuff in there, feel free to put together into something more generically useful. Unfortunately I don't have time for that, but I'm happy to answer any questions about it:
// env var configconstORIGIN_VOUCH_INTERNAL: string=env.get('ORIGIN_VOUCH_INTERNAL').required().asString();constAPP_FQDN: string=env.get('APP_FQDN').required().asString();constAPP_PORT: string=env.get('APP_PORT').default('443').asString();constAPP_FQDN_PLUS_PORT: string=`${APP_FQDN}${APP_PORT==="443" ? "" : ":"+APP_PORT}`;constVOUCH_ORIGIN_EXTERNAL=`https://oauth.${APP_FQDN_PLUS_PORT}`;constAUTH_ORIGIN_EXTERNAL=`https://${APP_FQDN_PLUS_PORT}`;constCOOKIE_MAX_AGE_SECONDS=parse('1 week','s');// ┌────────────────────────────────────────────────────────────────────────────────────────────────┐// │ href │// ├──────────┬──┬─────────────────────┬────────────────────────┬───────────────────────────┬───────┤// │ protocol │ │ auth │ host │ path │ hash │// │ │ │ ├─────────────────┬──────┼──────────┬────────────────┤ │// │ │ │ │ hostname │ port │ pathname │ search │ │// │ │ │ │ │ │ ├─┬──────────────┤ │// │ │ │ │ │ │ │ │ query │ │// " https: // user : pass @ sub.example.com : 8080 /p/a/t/h ? query=string #hash "// │ │ │ │ │ hostname │ port │ │ │ │// │ │ │ │ ├─────────────────┴──────┤ │ │ │// │ protocol │ │ username │ password │ host │ │ │ │// ├──────────┴──┼──────────┴──────────┼────────────────────────┤ │ │ │// │ origin │ │ origin │ pathname │ search │ hash │// ├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤// │ href │// └────────────────────────────────────────────────────────────────────────────────────────────────┘// (All spaces in the "" line should be ignored. They are purely for formatting.)exportdefaultfp(async(server: FastifyInstanceWithDB,_: PluginMetadata,next: any)=>{// see https://www.fastify.io/docs/latest/TypeScript/ to type headers and the bodyserver.get("/login",{},async(request: FastifyRequest,reply: FastifyReply)=>{consturlVouchValidate=`${ORIGIN_VOUCH_INTERNAL}/validate`;letvouchResponse: Response<string>;consthostDomain :string=request.hostname;constreferrerDomain :string=request.headers.referer ? newURL(request.headers.referer).hostname : '';// Think like a cookie: if we have a development server on a different domain when we redirect after a login// we go to the APP_FQDN server NOT the development server (which we want) so set a cookie to tell the// non-dev client to redirect to the dev serverif(hostDomain!==referrerDomain&&referrerDomain.endsWith('.localhost')){reply.setCookie('volatile_development_login_cookie',request.headers.referer,{sameSite: 'none',domain: APP_FQDN,maxAge: 10,httpOnly: false,path: '/',secure: true});// also tell the development server that they are authenticated, even tho technically they aren't YET// but this is the last time we have enough context to tell the dev serverreply.setCookie(`${referrerDomain}_authenticated`,'true',{sameSite: 'none',domain: referrerDomain,maxAge: COOKIE_MAX_AGE_SECONDS,httpOnly: false,path: '/',secure: true});}try{// only the cookie needs to be passed along to vouchvouchResponse=awaitgot.get(urlVouchValidate,{headers: {cookie: request.headers.cookie},throwHttpErrors: false});if(vouchResponse.statusCode===StatusCodes.UNAUTHORIZED){// create a 302 redirect as per vouch docs// normally handled with ngnix config but we need to do it here to// magically handle all the different use cases// see https://github.com/vouch/vouch-proxy// convention// we redirect back to THIS endpoint so that we can harvest the vouch JWT and get the user dataconsturlRedirect=`${VOUCH_ORIGIN_EXTERNAL}/login?url=${AUTH_ORIGIN_EXTERNAL}/login&vouch-failcount=&X-Vouch-Token=&error=`;console.log('urlRedirect',urlRedirect);returnreply.redirect(urlRedirect);}elseif(vouchResponse.statusCode!==StatusCodes.OK){request.log.error(`${urlVouchValidate} status=${vouchResponse.statusCode} body=${vouchResponse.body}`);returnreply.code(500).send('Internal error ugh');}// continue the main block}catch(err){request.log.error({error: `${err}`});returnreply.code(500).send('Internal error ugh');}// create the user if needed// create a new browser cookie session// add cookie to cacheconstemail: string=vouchResponse.headers["x-vouch-idp-claims-email"]asstring;constpicture: string|undefined=vouchResponse.headers["x-vouch-idp-claims-picture"]asstring;constvouch_success=vouchResponse.headers["x-vouch-success"]==='true';if(!vouch_success||!email||email===''){request.log.error(`${urlVouchValidate} status=${vouchResponse.statusCode} but no user found`);returnreply.code(500).send('Internal error ugh');}try{awaitserver.db.UpsertUser({ email, picture });constresponseGetUser=awaitserver.db.GetUserByEmail({ email });if(responseGetUser.users.length==0){request.log.error(`email=${email} error=Failed to find user after upsert`);returnreply.code(500).send('Internal error ugh');}constuser=responseGetUser.users[0];constuserId=user.id;consttokenResponse=awaitserver.db.CreateSessionToken({ userId });consttoken: string=tokenResponse.insert_tokens.returning[0].token;assert(token);// finally everything worked, we have a new app cookie// SameSite=None is required for the dev case, but also for things like embedded apps, which is most of my apps so far 🤷reply.setCookie(APP_FQDN,token,{sameSite: 'none',domain: APP_FQDN,maxAge: COOKIE_MAX_AGE_SECONDS,httpOnly: true,path: '/',secure: true});reply.setCookie(`${APP_FQDN}_authenticated`,'true',{sameSite: 'none',domain: APP_FQDN,maxAge: COOKIE_MAX_AGE_SECONDS,httpOnly: false,path: '/',secure: true});// clients cannot see the above cookie, but it's much easier for clients to know the state}catch(err){request.log.error(`Failed to upsert user or insert token email=${email} error=${err}`);returnreply.code(500).send('Internal error ugh');}// by default, redirect to the main app. Should this be configurable or dynamic?returnreply.redirect(`https://${APP_FQDN_PLUS_PORT}`);});next();});
The text was updated successfully, but these errors were encountered:
@dionjwa thanks again for the fine work! I've linked to this issue from the README under "advanced configurations"
There's talk of integration with Caddy and other http servers. If that gets built and documented I could see breaking this out into its own section in the docs as well.
Responding to #305 (comment) as an issue so as not to derail that discussion.
"I have a complete cloud stack template (app + ci + deploy in cloud providers with oauth). There are other oauth systems, but for a flexible simple single oauth service vouch is reliable and simple. I use node.js instead of nginx as the router of requests (maybe that config could be useful to others, idk it feels a pretty rare case). In a sense it replaces using Auth0, Okta, etc, or an integrated OAuth library like http://www.passportjs.org. There's just so much complexity, possible vendor lock-in, expense, etc, that sometimes a tool solving a single task is preferable, at least in the beginning."
Architecture:
The
auth
service (can be a cloud-function/lambda or horizontally scaling pod) handles the redirection thatnginx
performs in vouch's documentation.Here is my actual
/login
route handler, redirecting tovouch
when needed. There's some of my app specific stuff in there, feel free to put together into something more generically useful. Unfortunately I don't have time for that, but I'm happy to answer any questions about it:The text was updated successfully, but these errors were encountered: