Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling 2-way PKI authentication #2759

Open
bitsandbytes opened this issue Jul 10, 2017 · 20 comments
Open

Enabling 2-way PKI authentication #2759

bitsandbytes opened this issue Jul 10, 2017 · 20 comments

Comments

@bitsandbytes
Copy link

bitsandbytes commented Jul 10, 2017

I need 2-way authentication to be configurable because my company requires all internal webapps to use it.

The proposal is three-fold. I want the following to be configurable:

  • Configure the webapp to request the client's certificate,
  • Once the webapp receives the certificate pass information about the cert to the expressjs server,
  • Make the webapps' certificate and key configurable.

Background on this:

Without the above I can't easily test my passport setup and can't easily test my custom authentication code until I build for production.

My open questions are:

  • Is there any way to customize the webapp's configuration? It appears to me we have to rely on create-react-app to do it for us.
  • My understanding from getPeerCertificate() is an empty object when using dev server #1413 is that WDS will also need modifications in order for the webapp to pass cert attributes to the backend. I'll need to raise an issue for that over at WDS. Is this correct?
@craineum
Copy link

I also found myself wanting this feature. I am rolling progressive web app features onto a long lived project. I wanted to make sure that the issues I was seeing were not related to the browser ssl trust errors.

I completely get the convention, heuristics, or interactivity over configuration methodology (and appreciate it very much). In the case of SSL, I am already configuring my local to do the HTTPS thing. In my case specifically I have already configured my Rails app to use the certs that I generated, so I am reusing the two variables that I am already using for my Rails app.

I have code that accomplishes this. It basically is the third bullet point from @bitsandbytes original post: Make the webapps' certificate and key configurable.

I am reading in SSL_KEY_PATH and SSL_CERT_PATH from environment variables, and using those to configure the http server as document here on webpack devServer.

I have all the code in react-scripts/config/webpackDevServer.config.js, but it might better to move it to react-dev-utils/WebpackDevServerUtils.js and have a few minor tweaks to react-scripts.

I can do a PR (after a little code cleanup), but didn't know if the maintainers think this breaks the too much configuration rule.

@dbk91
Copy link

dbk91 commented Jul 25, 2017

@craineum looks like your implementation was similar to my approach. The only issue is that https does need to be a boolean because create-react-app ultimately sends that to webpack-dev-server, which expects the type to be a boolean. Please correct me if I'm missing any additional changes you made.

I have the pathnames to the certs in package.json rather than environment variables, though. Not sure if one or the other aligns more with create-react-app's way of convention, heuristics, and interactivity over configuration (which I, too, understand and think makes create-react-app a great tool!).

I initially tried leveraging as much of the proxy object configuration as I could in package.json because, according to the docs: "You may also specify any configuration value http-proxy-middleware or http-proxy supports.". In reality, we're constrained by the fact that this is configured in a JSON file, not in Javascript, so we can't read in things like buffers and functions. So that's where putting in pathnames to the cert and key makes sense. Additionally, if you wanted to send something like the DN to the API, you would need to write a function for that as you would for http-proxy-middleware.

So, really it appears there needs to be a PR for webpack-dev-server to accept the requestCert method before create-react-app could support fetching the client cert (if it aligns with the philosophy).

@craineum
Copy link

@dbk91 webpack-dev-server https can take an object as well. I also initially tried the proxy approach and ran into the same issues as you.

@dbk91
Copy link

dbk91 commented Aug 9, 2017

@craineum Ah, I see now. My mistake. I must have been thinking of my original issue with create-react-app where the webpackDevServer config file only can send a boolean instead of either a boolean or an object. https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/config/webpackDevServer.config.js#L81

@jamesmfriedman
Copy link
Contributor

I have the same issue, I need to pass a certificate file in to webpackDevServer like so

https: {
  key: path/to/key/file,
  cert: path/to/pem/file
}

@jamesmfriedman
Copy link
Contributor

jamesmfriedman commented Sep 28, 2017

Ok, this is nuts. I didn't even know this was possible with Node, but I found the hack when digging through the code in react-app-rewired. Monkey patch for the win...

  • Modify NPM start to be your own script, put that at the top, and then call then call the react script from inside of it.
  • Easier method, install react-app-rewired https://github.com/timarney/react-app-rewired and then put this code at the top of config-overrides.js
if (process.env.NODE_ENV === 'development') {
  const devServerConfigPath = 'react-scripts/config/webpackDevServer.config';
  const devServerConfig = require(devServerConfigPath);
  require.cache[require.resolve(devServerConfigPath)].exports = (
    proxy,
    allowedHost
  ) => {
    const conf = devServerConfig(proxy, allowedHost);
    conf.https = {
      key: fs.readFileSync('./path/to/keyfile.key'),
      cert: fs.readFileSync('./path/to/pemfile.pem')
    };

    return conf;
  };
}

@tobias74
Copy link

tobias74 commented Oct 1, 2017

@jamesmfriedman where exactly do I put this snippet of code?

@jamesmfriedman
Copy link
Contributor

@tobias74 Sorry for the lack of context. A couple of options for you...

  • Modify NPM start to be your own script, put that at the top, and then call then call the react script from inside of it
  • Easier method, install react-app-rewired https://github.com/timarney/react-app-rewired and then put that at the top of config-overrides.js

@Makr91
Copy link

Makr91 commented Nov 11, 2017

I got the react-app-rewired working, but when I got to add your conf and modify the key and cert names, it fails to start, I am assuming I have them in the wrong directory, can you clarify if these should be inside of the react-scripts/config folder?

The override works without the :

conf.https = { key: fs.readFileSync( path.join(root, 'config', 'quickcrashapp.local.key') ), cert: fs.readFileSync( path.join(root, 'config', 'quickcrashapp.local.pem') ) };

@jamesmfriedman
Copy link
Contributor

@Makr91 root is just a variable for a path, you'll have to define the appropriate path to your file. I'll edit my previous comment to reflect this.

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

Does anyone want to submit a PR to support this in some way without monkeypatching?
I haven't seen a specific proposal for how it should work so it's hard to say anything.

@AlexanderHolman146
Copy link

@jamesmfriedman That's a great suggestion with React App Rewired. I am using a cert/key from a local CA (which I manually added to my keychain). I have localhost:3000 (react server) running via chrome successfully (green lock :) ). However, when I try to navigate to localhost:5000/auth/googleoauth/signup (starting the oauth flow), I get the following error:

Proxy error: Could not proxy request /api/auth/googleoauth/signup from localhost:3000 to https://localhost:5000 (UNABLE_TO_VERIFY_LEAF_SIGNATURE).

My set up is pretty standard. I am using the same key / cert for the react server as my express / node server, but I assume that's ok because one is proxying the other.

Do you have any ideas of how I can fix this? Did you see this in your travels?

@lukewlms
Copy link

@AlexanderHolman146 How did you get the green lock with create-react-app? I'm still stuck there.

@jamesmfriedman
Copy link
Contributor

@AlexanderHolman146 it took me forever to figure this out... Replace the stuff in caps with whatever makes sense for your setup.

Make a file called ssl.conf.

This file has to be in the directory you are running the bash command from (or you need to update the config path in the bash script)

[ req ]
distinguished_name  = req_distinguished_name
req_extensions = v3_req
 
[ req_distinguished_name ]
0.organizationName		= Organization Name (eg, company)
commonName      = Common Name (e.g. server FQDN or YOUR name)
commonName_max      = 64
 
[ v3_req ]
basicConstraints = critical,CA:true,pathlen:1
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, keyCertSign
subjectAltName = @alt_names
 
[alt_names]
DNS.1 = YOUR-LOCAL-DOMAIN-Name.local
DNS.2 = localhost
openssl req \
 	-new \
         -x509 \
 	-days 3650 \
 	-sha256 \
 	-nodes \
	-extensions v3_req \
 	-out ./config/YOUR-OUT-FILE-NAME.local.pem \
 	-newkey rsa:2048 \
 	-keyout ./config/YOUR-OUT-FILE-NAME.local.key \
 	-config <( cat ./ssl.conf )

@brownbl1
Copy link

I have a similar use case that motivated me to go down this road. I'm developing a tab app for Microsoft Teams. Their tabs are essentially iframed web apps, but they don't allow http://localhost, even for development (you can upload a dev manifest that points to your local site but it just can't be localhost). So I've been modifying my hosts file to something like 127.0.0.1 myapp-local.com. Setting HTTPS=true allows me to access https://myapp-local.com:3000 and everything works.

Except not quite, because the generated SSL cert is regenerated every time you restart the development server. But the bigger problem is that Teams is an electron app, so there is no way to dangerously bypass the invalid SSL warning that is accessible in chrome. This leaves me with the need to add the generated cert to my root CA store for my local machine. This works if I am hitting https://localhost:3000 but I'm not hitting localhost, I'm hitting https://myapp-local.com:3000. But it seems that the certificate is generated for localhost and not the value specified in the HOST variable. Hmm..

So I was able to get the monkey patching approach working as laid out by @jamesmfriedman (thanks for that as a short term solution).

I also used his openssl script to generate my certs (I may never have figured that out). The key point seems to be that if your goal is to turn the browser lock icon green, it's not enough to just generate a normal ssl cert. You need it to be a CA with the correct DNS specifiers. I could be all wrong about this, but I couldn't get anything other than this to work.

But I agree with @gaearon that this monkey patching isn't ideal, and I'd love to help out with a PR (or direction towards one) if I can.

From my experience, I see a few things that would be steps in the right direction:

  • not generating a new SSL certificate every time yarn start is run (I realize it's being done by webpack, so this is non-trivial?)
  • somehow forcing the generated certificate to be generated for the domain set in HOST (or fallback to localhost)
  • allow the user to specify their own certificate in a configuration section of some sort

The last option is more flexible, but ultimately isn't what I really am after here. And honestly, it's a lot more work to specify your own cert than to have those other first two points just work. So I would shoot for those first two points as a first good goal that would increase usability and leave the open-ended configuration piece left for last.

And finally, I'm not convinced all of this makes sense. There may be a better way or something I'm missing so please chime in on this aging thread if you want to steer my thinking in a different direction.

Thanks!

@hansena
Copy link

hansena commented Apr 4, 2019

@gaearon is this still something you would be open to a PR for? The change would be contained to packages/react-scripts/config/webpackDevServer.config.js. Based on devServer.https it could go something like replacing

const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';

with

let https;
try {
  const { ca, cert, key } = JSON.parse(process.env.HTTPS);
  if (ca || cert || key)
    https = {
      ...(ca && { ca: fs.readFileSync(ca) }),
      ...(cert && { cert: fs.readFileSync(cert) }),
      ...(key && { key: fs.readFileSync(key) }),
    };
} catch (e) {
  if (process.env.HTTPS) throw new Error(e);
}
if (process.env.HTTPS === 'true') https = true;

And

module.exports = function(proxy, allowedHost) {
-  https: protocol === 'https',
+  ...(https && { https }),

@dbk91
Copy link

dbk91 commented Apr 4, 2019

@hansena Naive assumption here, but I'm inclined to believe that version 2 of create-react-app probably handles all of the use-cases mentioned in this issue. They added a nice feature to configure and override the development proxy server: https://facebook.github.io/create-react-app/docs/proxying-api-requests-in-development#configuring-the-proxy-manually

Provided you can pass in the desired SSL options (which it looks like you can just interact with the normal http-proxy-middleware object), this would probably be the way to go as you would not have to rely on passing file paths in package.json.

I'll try to see if I can get a working example this weekend to confirm that this is possible in the latest version.

@nearwood
Copy link

nearwood commented May 6, 2019

@dbk91 Did you get an example working? This would be really useful for my project.

@mikekellyio
Copy link

@dbk91 I'm also very interested in a working example

@dbk91
Copy link

dbk91 commented Aug 28, 2019

Okay, so I finally got the chance to look into it. Unfortunately, as far as I can tell, my assumption was both naive and incorrect.

I was assuming you could configure the server on the fly through the src/setupProxy.js like so...

const fs = require('fs');
const path = require('path');
const proxy = require('http-proxy-middleware');

// Just reading certificates from outside src/
const key = fs.readFileSync(path.resolve(__dirname, '..', 'localhost-key.pem'));
const cert = fs.readFileSync(path.resolve(__dirname, '..', 'localhost.pem'));

module.exports = function(app) {
  app.use(proxy('*', { target: 'localhost:4000', ssl: { key, cert } }));
};

However, this is for the proxy middleware, not the dev server configuration itself. I don't think any of the configuration parameters a user can pass in could override it. Here's the spot that tells the dev server to use self-signed certificates. I believe that's because passing a boolean creates self-signed certificates using webpack-dev-server. If there was a way to override that https variable with an object containing the paths to the key and cert rather than a boolean, you'd have a dev server using you certs of choice (I did confirm this using certs of my own).

It may still be possible to have something override the config, but if there isn't, it's going to take a pull request to get this feature in. I'd say it's probably low-priority for the CRA team, and the way one implements it is important. But I'd say this is a useful feature. I think the lowest effort thing to do would be to allow the user to plop a directory with a specific name, and if there is a key and a cert in there, CRA will use it as the development certificates. This would probably live next to src/setupProxy.js in both implementation and documentation.

dbk91 added a commit to dbk91/create-react-app that referenced this issue Aug 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests