Security of secrets added to next.config.js - next.js

We are working adding Auth0 to our Next.js website and referencing this example.
What I am wondering about is the settings in next.config.js in the example. It puts the Auth0 and other secrets in the client (via Webpack). Doesn't this put these secrets at risk? Since they are somewhere in the client code, there is a chance that a request can be made to access the secrets.
Examples in this Auth0 article also puts the secrets in the client.
I haven't had much luck finding out how Webpack handles the variables and am looking to the community to shed some light on this. We are trying to ensure our pattern is safe before putting it in to place.
From example, secrets being added to client side next.config.js:
const dotenv = require('dotenv')
dotenv.config()
module.exports = {
env: {
AUTH0_DOMAIN: process.env.AUTH0_DOMAIN,
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
AUTH0_SCOPE: 'openid profile',
REDIRECT_URI:
process.env.REDIRECT_URI || 'http://localhost:3000/api/callback',
POST_LOGOUT_REDIRECT_URI:
process.env.POST_LOGOUT_REDIRECT_URI || 'http://localhost:3000/',
SESSION_COOKIE_SECRET: process.env.SESSION_COOKIE_SECRET,
SESSION_COOKIE_LIFETIME: 7200, // 2 hours
},
}

Update - Next v9.4:
Since Next.js v9.4, it exposes only env variables with the prefix NEXT_PUBLIC_ to the browser.
For more info, read this
Original answer:
DON'T put any secret env variables in a place that is accessible to the client.
I'm not sure what next does with this env property, It just configures a webpack DefinePlugin that replaces usages of process.env.VAR to it's value.
So, this means that your secrets will be inside bundles that are public.
To confirm that it is exposed in the client,
open dev-tools
open console by
pressing esc
click on the search tab
enter your secret key
It will find it in one of the bundles.

Related

Getting "API Key not found. Please pass a valid API key" from GCP Identity Platform but the Key is absolutely correct

I am trying to follow the tutorial here:
https://www.youtube.com/watch?v=ny92vcpOQFs
...but I get a 400 error back with the message "API Key not found. Please pass a valid API key".
The code section where the API key is provided is literally copied right from the GCP Identity Platform interface, and I have checked it multiple times.
I don't know where to go from here, all the tutorials just take it for granted that this works.
The minimal reproduction is as follows, although I get the same result if I try to use firebase-ui-auth:
<!DOCTYPE html>
<html>
<head>
<title>Identity Platform Test</title>
<script src="https://www.gstatic.com/firebasejs/8.0/firebase.js"></script>
<script> //this section copied directly from GCP
var config = {
apiKey: '<Private but 100% correct>',
authDomain: 'my-site.firebaseapp.com',
};
firebase.initializeApp(config);
</script>
<script>
let email = 'private#gmail.com';
let pw = 'private';
firebase
.auth()
.signInWithEmailAndPassword(email, pw)
.then((ac) => {
console.log('It worked.');
})
.catch((error) => {
console.log(error.toString());
});
</script>
</head>
<body>
Test
</body>
</html>
Just an update to say I've followed the instructions on both of these pages, made sure I have all the pre-requisites, etc. I feel like there's some underlying assumption that is made but not communicated:
https://medium.com/#ThatJenPerson/whos-there-implementing-identity-platform-for-web-c210c6839d3b
https://cloud.google.com/identity-platform/docs/web/google
It seems like a bug in GCP/Identity Platform.
If you start in Identity Platform, it will generate an API key for you. If you look at the Firebase console, the API Key will match there. ...but it won't work (for anything it seems).
If you then create an app (seemingly any app) it will regenerate the project API Key (which doesn't seem right). You don't need any of the App configuration information - just the apiKey and authDomain are required from the project, but now the API key (at the project level, not the App level) is regenerated (and GCP Identity Platform will now reflect this new key), and that newly generated API Key will work correctly.
The app can just be an HTML file sitting on your local computer and it will still work - but only if you set up SOME app in Firebase (even though you never use it for anything).
It seems problematic that both the projectId and appId are missing from the config. In my multiple Firebase authentication implementations, I always include those.
Inside your Firebase Project Settings, scroll down and copy the entire config just to be certain:
https://console.firebase.google.com/u/0/project/<YOUR_PROJECT_NAME_HERE>/settings/general
**EDIT (MORE INFO)
After visiting https://console.firebase.google.com and selecting your project, click the little gear icon in the top left > Project Settings > (scroll all the way down to the code sample they give you)
You would see something like this:
In this screenshot (purposely cut-off) you'll see:
const firebaseCon....
That's the entire config you need to be using - I've setup dozens of Firebase Firestore projects with all services and I don't leave out the projectId and appId ever.
Another thing to make sure of is that your Firebase Authentication has Email / Password enabled. Go to Build > Authentication > Sign-in method. Then make sure you've added and enabled Email/Password option.

id/refresh token settings are turned off for beforeCreate/beforeSignIn EVERY FUNCTIONS DEPLOY

Although initially enabled, after every firebase deploy --only:functions id/refresh token settings are all disabled:
One has to manually re-enable each time which is super frustrating!
Perhaps this is because a deployment might change the blocking functions (particularly true when transpiling from typescript, etc.).
Is there a way to make these settings "sticky" across deployments?
Alternative suggestion for the world-class firebase team:
Add a new field to firebase.json:
"authentication": { "blockingFunctions": { "refreshToken": true, ...etc } }
Add a checkbox to the configuration UI something like [X] Allow application to manage these settings which, if checked, causes the firebase.json settings to take effect.

Hiding Storyblok API-Key

I'm using Next.js with Storyblok and recently made use of the react-next-boilerplate.
I noticed that they put the preview token in the _app.js, so essentially publish it:
storyblokInit({
accessToken: "your-preview-token",
use: [apiPlugin],
components,
});
If I use an environment variable instead, which isn't available on the client, I get the error
You need to provide an access token to interact with Storyblok API
in the client. That's because (I think) my components use StoryblokComponent, which makes use of the global Storyblok state. So I wonder:
Should I ignore this error, as I don't plan to interact with the Storyblok API other than using it for component rendering (all the data comes from the server, as far as I understand the concept of static site generation), and component rendering seems to be still working?
Should I just publish the preview token?
Should I create two tokens, one for the server and one for the client?
Setting the token to process.env.STORYBLOK_API_KEY || "NULL" (where "NULL" can be anything except the empty string) also works (no more errors) but seems like a weird solution.
I don't really understand why they combine these two things, component rendering and data fetching, in the same function.
I would use a .env.local file and populate it with:
STORYBLOK_API_KEY=your-preview-token
To use the environment variable inside _app.js you have to pass it to next.config.js like this:
module.exports = {
env: {
STORYBLOK_API_KEY: process.env.STORYBLOK_API_KEY,
}
}
Source: https://nextjs.org/docs/basic-features/environment-variables

Firebase 3rd-party AuthProvider (Google/Facebook/etc) login with chrome extension manifest v3

Manifest version 3 for Chrome extensions have been killing me lately. Been able to navigate around it so far, but this one has really stumped me. I'm trying to use Firebase authentication for a Chrome extension, specifically with 3rd party auth providers such as Google and Facebook. I've setup the Firebase configuration for Login with Google and created a login section in the options page of the Chrome extension and setup the Firebase SDK.
Now, there are two login options when using an auth provider, signInWithRedirect and signInWithPopup. I've tried both of these and both have failed for different reasons. signInWithRedirect seems like a complete dead end as it redirects to the auth provider, and when it attempts to redirect back to the chrome-extension://.../options.html page, it just redirects to "about:blank#blocked" instead.
When attempting to use signInWithPopup, I instead get
Refused to load the script 'https://apis.google.com/js/api.js?onload=__iframefcb776751' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
In v2, you could simply add https://apis.google.com to the content_security_policy in the manifest. But in v3, the docs say
"In addition, MV3 disallows certain CSP modifications for extension_pages that were permitted in MV2. The script-src, object-src, and worker-src directives may only have the following values:"
self
none
Any localhost source, (http://localhost, http://127.0.0.1, or any port on those domains)
So is there seriously no way for a Google Chrome extension to authenticate with a Google auth provider through Google's Firebase? The only workaround I can think of is to create some hosted site that does the authentication, have the Chrome extension inject a content script, and have the hosted site pass the auth details back to the Chrome extension through an event or something. Seems like a huge hack though and possibly subject to security flaws. Anyone else have ideas??
Although it was mentioned in the comments that this works with the Google auth provider using chrome.identity sadly there was no code example so I had to figure out myself how to do it.
Here is how I did it following this tutorial:
(It also mentions a solution for non-Google auth providers that I didn't try)
Identity Permission
First you need permission to use the chrome identity API. You get it by adding this to your manifest.json:
{
...
"permissions": [
"identity"
],
...
}
Consistent Application ID
You need your application ID consistent during development to use the OAuth process. To accomplish that, you need to copy the key in an installed version of your manifest.json.
To get a suitable key value, first install your extension from a .crx file (you may need to upload your extension or package it manually). Then, in your user data directory (on macOS it is ~/Library/Application\ Support/Google/Chrome), look in the file Default/Extensions/EXTENSION_ID/EXTENSION_VERSION/manifest.json. You will see the key value filled in there.
{
...
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgFbIrnF3oWbqomZh8CHzkTE9MxD/4tVmCTJ3JYSzYhtVnX7tVAbXZRRPuYLavIFaS15tojlRNRhfOdvyTXew+RaSJjOIzdo30byBU3C4mJAtRtSjb+U9fAsJxStVpXvdQrYNNFCCx/85T6oJX3qDsYexFCs/9doGqzhCc5RvN+W4jbQlfz7n+TiT8TtPBKrQWGLYjbEdNpPnvnorJBMys/yob82cglpqbWI36sTSGwQxjgQbp3b4mnQ2R0gzOcY41cMOw8JqSl6aXdYfHBTLxCy+gz9RCQYNUhDewxE1DeoEgAh21956oKJ8Sn7FacyMyNcnWvNhlMzPtr/0RUK7nQIDAQAB",
...
}
Copy this line to your source manifest.json.
Register your Extension with Google Cloud APIs
You need to register your app in the Google APIs Console to get the client ID:
Search for the API you what to use and make sure it is activated in your project. In my case Cloud Firestore API.
Go to the API Access navigation menu item and click on the Create an OAuth 2.0 client ID... blue button.
Select Chrome Application and enter your application ID (same ID displayed in the extensions management page).
Put this client ID in your manifest.json. You only need the userinfo.email scope.
{
...
"oauth2": {
"client_id": "171239695530-3mbapmkhai2m0qjb2jgjp097c7jmmhc3.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email"
]
}
...
}
Get and Use the Google Auth Token
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// console.log("token: " + token);
let credential = firebase.auth.GoogleAuthProvider.credential(null, token);
firebase.auth().signInWithCredential(credential)
.then((result) => {
// console.log("Login successful!");
DoWhatYouWantWithTheUserObject(result.user);
})
.catch((error) => {
console.error(error);
});
});
Have fun with your Firebase Service...

Oauth2 Authorization in NelmioApiDocBundle

I am trying to use the NelmioApiDocBundle for a Symfony 3.4 projects API documentation, while also trying to wrap my head around OAuth 2 authorization for the project API access to begin with.
So far I've followed this tutorial on how to get FOSOAuthServerBundle working. So far I can
1.) create a client using the command line command:
php bin/console fos:oauth-server:create-client --redirect-uri="___" --grant-type="authorization_code" --grant-type="password" --grant-type="refresh_token" --grant-type="token" --grant-type="client_credentials"
2.) I can also get an access token manually by visiting this url on my server
http://127.0.0.1:8000/oauth/v2/token?client_id=______&client_secret=________&grant_type=client_credentials
3.) I can use the token to access areas of my Symfony project requiring OAuth Access by including the token in a GET parameter
However, in the NelmioApiDocBundle Authorizations I cannot get this to work to completion. Here is a screenshot:
If enter my client_id and secret key it takes me to the Login Page, as expected. I can enter my login information and in takes me to the Approve or Deny Page, as expected. At this point if I click either Approve or Deny it tries to use a "redirect_uri" of http://localhost:3200/oauth2-redirect.html. No matter what I do I cannot change the redirect URI.
How to I get the a proper redirect URI?
Ok, this was actually easily fixed. You need to add a single line:
oauth2RedirectUrl: 'URLhere',
to the file init-swagger-ui.js which is located (Symfony 3.4) in web/bundles/nelmioapidoc/
The final file ended up looking like this:
window.onload = () => {
const data = JSON.parse(document.getElementById('swagger-data').innerText);
const ui = SwaggerUIBundle({
oauth2RedirectUrl: 'URLhere',
spec: data.spec,
dom_id: '#swagger-ui',
validatorUrl: null,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: 'StandaloneLayout'
});
window.ui = ui;
};
Also you likely are going to want to download the file oauth2-redirect.html from the Swagger project to include for the actual redirect.

Resources