I am using ghost, i made an integration and i would like to hide the api key from the front-end. I do not believe i can set restrictions on the ghost cms (that would also work). And i do believe so +page.js files are run on the browser also, so im a little confused on how to achieve this?
The interal sveltekit module $env/static/private (docs) is how you use secure API keys. Sveltekit will not allow you to import this module into client code so it provides an extra layer of safety. Vite automatically loads your enviroment variables from .env files and process.env on build and injects your key into your server side bundle.
import { API_KEY } from '$env/static/private';
// Use your secret
Sveltekit has 4 modules for accessing enviroment variables
$env/static/private (covered)
$env/static/public accessiable by server and client and injected at build (docs)
$env/dynamic/private provided by your runtime adapter; only includes variables with that do not start with the your public prefix which defaults to PUBLIC_ and can only be imported by server files (docs)
$env/dynamic/public provided by your runtime adapter; only includes variables with that do start with the your public prefix which defaults to PUBLIC_ (docs)
You don't need to hide the key.
Ghost Content API Docs:
These keys are safe for use in browsers and other insecure environments, as they only ever provide access to public data.
One common way to hide your third-party API key(s) from public view is to set up proxy API routes.
The general idea is to have your client (browser) query a proxy API route that you provide/host, have that proxy route query the third-party API using your credentials (API key), and pass on the results from the third-party API back to the client.
Because the query to the third-party API takes place exclusively on the back-end, your credentials are never exposed to the client (browser) and thus not visible to the public.
In your use case, you would have to create 3 dynamic endpoint routes to replicate the structure of Ghost's API:
src/routes/api/[resource]/+server.js to match /posts/, /authors/, /tags/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/[id]/+server.js to match /posts/{id}/, /authors/{id}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, id } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/${id}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
src/routes/api/[resource]/slug/[slug]/+server.js to match /posts/slug/{slug}/, /authors/slug/{slug}/, etc.:
const API_KEY = <your_api_key>; // preferably pulled from ENV
const GHOST_URL = `https://<your_ghost_admin_domain>/ghost/api/content`;
export function GET({ params, url }) {
const { resource, slug } = params;
const queryString = url.searchParams.toString();
return fetch(`${GHOST_URL}/${resource}/slug/${slug}/?key=${API_KEY}${queryString ? `&${queryString}` : ''}`, {
headers: {
'Accept-Version': '5.0' // Ghost API Version setting
}
});
}
Then all you have to do is call your proxy routes in place of your original third-party API routes in your app:
// very barebones example
<script>
let uri;
let data;
async function get() {
const res = await fetch(`/api/${uri}`);
data = await res.json();
}
</script>
<input name="uri" bind:value={uri} />
<button on:click={get}>GET</button>
{data}
Note that using proxy API routes will also have the additional benefit of sidestepping potential CORS issues.
Related
I have a backend write with Nodejs that can connect to contracts and perform functions in those contracts but the problem is I want my Metamask can pass only the account address to the backend. Is there any solution for this?
there are some third party packages like node-metamask
you can use them
add this code snippet in your views (hbs template engine) / HTML file.
When you load this page, the script will execute and it will get the metamask wallet address if it's installed.
<script>
async function connect()
{
if (window.ethereum) {
await window.ethereum.request({ method: "eth_requestAccounts" });
window.web3 = new Web3(window.ethereum);
const account = web3.eth.accounts;
const walletAddress = account.givenProvider.selectedAddress;
console.log(`Wallet: ${walletAddress}`);
window.location.href = `/setup?wallet=${walletAddress}`;
}
else {
alert("MetaMask is not installed");
}
}
</script>
after that, It will send the wallet address at a specific backend route to the express.
hence, you can perform actions in node JS with the integration of web3.
app.get('/setup', (req, res) => {
const address = req.query.wallet
app.locals.address = address
console.log(app.locals.address)
})
here's complete code example: https://github.com/billypentester/web3-dapp
I am using #google-cloud/local-auth to auth an application on a node back end. I have set up a OAuth 2.0 Client IDs with the type of Web application on the google console and pointed the Authorized redirect URIs to http://localhost:3001 because that is the port I am using for dev, however I can see that the source code for #google-cloud/local-auth/build/src/index.js:56:15 clearly expects 3000
if (redirectUri.length === 0 ||
parts.port !== '3000' ||
parts.hostname !== 'localhost' ||
parts.pathname !== '/oauth2callback') {
throw new Error(invalidRedirectUri);
}
Why would this be hardcoded, what happens when you use a diferent port?
I am adding my code for context
import gsc from '#googleapis/searchconsole'
import { authenticate } from '#google-cloud/local-auth'
import { resolve } from 'path'
export default async () => {
// Obtain user credentials to use for the request
const auth = await authenticate({
keyfilePath: resolve('requests/client_secret.json'),
scopes: [
'https://www.googleapis.com/auth/webmasters',
'https://www.googleapis.com/auth/webmasters.readonly'
]
})
google.options({ auth })
const res = await gsc.sites.list({})
console.log(res.data)
}
This library is meant to demonstrate authentication for sample purposes; it should be treated as a starting point for building an application and is not a general-purpose solution. Read it here
That port number is expected since this library is for demonstration purposes, if you are developing a production-ready application, you need to use something like https://github.com/googleapis/google-auth-library-nodejs
I have the following apolloClient
/**
* Initializes an ApolloClient instance. For configuration values refer to the following page
* https://www.apollographql.com/docs/react/api/core/ApolloClient/#the-apolloclient-constructor
*
* #returns ApolloClient
*/
const createApolloClient = (authToken: string | null) => {
const httpLinkHeaders = {
...(authToken && { Authorization: `Bearer ${authToken}` })
};
console.log('CREATING APOLLO CLIENT WITH HEADERS >>>>', httpLinkHeaders);
console.log(
'Graph Env Variable URL >>>>>',
publicRuntimeConfig.GRAPHQL_URL
);
return new ApolloClient({
name: 'client',
ssrMode: typeof window === 'undefined',
link: createHttpLink({
uri: publicRuntimeConfig.GRAPHQL_URL,
credentials: 'same-origin',
headers: httpLinkHeaders
}),
cache: new InMemoryCache()
});
};
/**
* Initializes the apollo client with data restored from the cache for pages that fetch data
* using either getStaticProps or getServerSideProps methods
*
* #param accessToken
* #param initialState
*
* #returns ApolloClient
*/
export const initializeApollo = (
accessToken: string,
initialState = null,
forceNewInstane = false
): ApolloClient<NormalizedCacheObject> => {
// Regenerate client?
if (forceNewInstane) {
apolloClient = null;
}
const _apolloClient = apolloClient || createApolloClient(accessToken);
// for pages that have Next.js data fetching methods that use Apollo Client,
// the initial state gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract();
// Restore the cache using the data passed from
// getStaticProps/getServerSideProps combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState });
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
};
/**
* Hook to initialize the apollo client only when state has changed.
*
* #param initialState
*
* #returns
*/
export const useApollo = (
initialState: any
): ApolloClient<NormalizedCacheObject> => {
return useMemo(() => {
if (process.browser) {
const accessToken = extractCookie(document.cookie, 'access_token');
return initializeApollo(accessToken, initialState);
}
// document is not present and we can't retrieve the token but ApolloProvider requires to pass a client
return initializeApollo(null, initialState);
}, [initialState]);
};
That is initialized in the _app.tsx file like so
const updateApolloWithNewToken = useCallback(
(accessToken: string) => {
// Initialize apollo client with new access token
setClient(
initializeApollo(accessToken, pageProps.initialApolloState, true)
);
// Show the dashboard
router.replace('/dashboard');
},
[router]
);
With the following Next Config
const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
module.exports = (phase, { defaultConfig }) => {
console.log('Phase >>>>', phase);
if (phase === PHASE_DEVELOPMENT_SERVER) {
console.log('RETURNING DEVELOPMENT CONFIGURATION...');
return {
publicRuntimeConfig: {
GRAPHQL_URL: process.env.GRAPHQL_URL
}
};
}
console.log('RETURNING PRODUCTION CONFIGURATION...');
console.log('GRAPHQL_URL', process.env.GRAPHQL_URL);
return {
publicRuntimeConfig: {
GRAPHQL_URL: process.env.GRAPHQL_URL
}
};
};
This is my _app.tsx
function MyApp({ Component, pageProps }: AppProps) {
// Grab the apollo client instance with state hydration from the pageProps
const router = useRouter();
const apolloClient = useApollo(pageProps.initialApolloState);
const [client, setClient] = useState(apolloClient);
React.useEffect(() => {
// Refresh token on browser load or page regresh
handleAcquireTokenSilent();
// We also set up an interval of 5 mins to check if token needs to be refreshed
const refreshTokenInterval = setInterval(() => {
handleAcquireTokenSilent();
}, REFRESH_TOKEN_SILENTLY_INTERVAL);
return () => {
clearInterval(refreshTokenInterval);
};
}, []);
const updateApolloWithNewToken = useCallback(
(accessToken: string) => {
// Initialize apollo client with new access token
setClient(
initializeApollo(accessToken, pageProps.initialApolloState, true)
);
// Show the dashboard
router.replace('/dashboard');
},
[router]
);
return pageProps.isAuthenticated || pageProps.shouldPageHandleUnAuthorize ? (
<ApolloProvider client={client}>
<ThemeProvider theme={theme}>
<SCThemeProvider theme={theme}>
<StylesProvider injectFirst>
<Component
{...pageProps}
updateAuthToken={updateApolloWithNewToken}
/>
</StylesProvider>
</SCThemeProvider>
</ThemeProvider>
</ApolloProvider>
) : (
<UnAuthorize />
);
}
/**
* Fetches the Me query so that pages/components can grab it from the cache in the
* client.
*
* Note: This disables the ability to perform automatic static optimization, causing
* every page in the app to be server-side rendered.
*/
MyApp.getInitialProps = async (appContext: AppContext) => {
const appProps = await App.getInitialProps(appContext);
const req = appContext.ctx.req;
// Execute Me query and handle all scenarios including, unauthorized response, caching data so pages can grab
// data from the cache
return await handleMeQuery(appProps, req);
};
export default MyApp;
My problem is that when I run yarn build I get a server error generating 500 page. I know it is because when creating the Apollo Client it doesn't have access to the publicRuntimeConfig, it seems like Next is trying to build the ApolloClient when I run yarn build, I am using getInitialProps and getServerSideProps so I just want to access all the env variables on runtime not on build, because we want one build for our pipeline.
All other env variables in my app that are using publicRuntimeConfig are working, I tested by removing the env vars on build and adding them back on start and the app functioned as normal.
If there isn't a way to do this with apollo client, what would be reccomended to be able to pass different uri's as env variables on start of the app not on build for Apollo Client or alternate solution?
Thanks for any help ahead of time
So I don't know if I have explained the problem well enough.
Basically the graphql URL is different depending on the environment it is in in development, staging, and production however they are supposed to use the same build so I need to access the GRAPHQL_URL via a runtime variable, but in my current setup it is just undefined.
Generally you don't want to use publicRuntimeConfig because it adds overhead and is unnecessary for your use-case. It also disables automatic static optimization.
Traditionally, environment variables are the way to handle dynamic settings based on the environment. Next.js has three default environments – development, test, and production.
Your Next.js app will automatically grab (and merge) the correct variables depending on the environment.
.env.local
GRAPHQL_URL = localhost:8000/graphql
.env.test
GRAPHQL_URL = test.example.com/graphql
.env or .env.production
GRAPHQL_URL = production.example.com/graphql
Apollo config
new ApolloClient({
link: createHttpLink({
uri: process.env.GRAPHQL_URL,
})
});
Environment variable files
In your project root you can create files named
.env
.env.local
.env.test
.env.test.local
.env.production
I believe you can append .local to any environment to create a local only version .env.production.local - this has limited use in your case
Next.js Environment variables
All environments - .env
loaded in development, test, and production environments. This file is used for defaults across all environments. The variables in this file will be overroad if another environment variable file has a variable with the same name.
Development environment .env.local
loaded only when NODE_ENV = development (next dev)
Test environment .env.test
loaded only when NODE_ENV = test
Test environment .env.test.local
loaded only when NODE_ENV = test and local
Production environment .env.production
loaded only when NODE_ENV = production (next start)
Example production only variable
Create a .env.production and add the variable there. You can repeat the same for test and local only variables.
Notes
It's good practice to add your .env to your .gitignore so you don't accidently commit secrets to your repositories. At a minimum you should omit .env*.local files from git.
Depending on your CI/CD setup you can set your environment variables in the deployment platform like vercel, github actions, etc. This would allow you to set the test and production variables in the hosting platform and not in your code.
If you need an environment variable accessible in the browser you need to prefix the variabel with NEXT_PUBLIC_
First, there is redundant code and inefficiency. Primarily the hooks updateApolloWithNewToken and useApollo but also in the way you inject the accessToken.
I would recommend throwing the ApolloClient in it's own separate file and using it's configurable links for your use-case.
However, the real problem most likely lies in the initialization of the client and your attempt at memoizing the client.
First, try updating the following,
link: createHttpLink({
uri: publicRuntimeConfig.GRAPHQL_URL,
credentials: 'same-origin',
headers: httpLinkHeaders
}),
to
link: createHttpLink({
// ...your other stuff
uri: () => getConfig().publicRuntimeConfig.GRAPHQL_URL
})
If that doesn't work right away, I would recommend trying with the outmost minimal example.
Create a client you export, a provider and a component that uses it (without using state, useEffect or anything else). We can go from there if that still doesn't work.
I tried to Integrate OIDC Provider to Node JS and I have a Sample Code. So, I run this Sample code it's throwing an error(unrecognized route or not allowed method (GET on /api/v1/.well-known/openid-configuration)).The problem is Issuer(https://localhost:3000) this Issuer is working fine. but i will change this Issuer((https://localhost:3000/api/v1/)) it's not working How to fix this Issue and I facing another issue also when I implement oldc-provider in node js. They Routes are override how to fix this issue
Sample.js
const { Provider } = require('oidc-provider');
const configuration = {
// ... see available options /docs
clients: [{
client_id: 'foo',
client_secret: 'bar',
redirect_uris: ['http://localhost:3000/api/v1/'],
true_provider: "pcc"
// + other client properties
}],
};
const oidc = new Provider('http://localhost:3000/api/v1/', configuration);
// express/nodejs style application callback (req, res, next) for use with express apps, see /examples/express.js
oidc.callback()
// or just expose a server standalone, see /examples/standalone.js
const server = oidc.listen(3000, () => {
console.log('oidc-provider listening on port 3000, check http://localhost:3000/api/v1/.well-known/openid-configuration');
});
Error
Defining Issuer Identifier with a path component does not affect anything route-wise.
You have two options, either mount the provider to a path (see docs), or define the actual paths you want for each endpoint to be prefixed (see docs).
I think you're looking for a way to mount, so the first one.
so may be this is very basic question so please bear with me. Let me explain what I am doing and what I really need.
EXPLANATION
I have created a graphql server by using ApolloGraphql (apollo-server-express npm module).
Here is the code snippet to give you an idea.
api.js
import express from 'express'
import rootSchema from './root-schema'
.... // some extra code
app = express.router()
app.use(jwtaAuthenticator) // --> this code authenticates Authorization header
.... // some more middleware's added
const graphQLServer = new ApolloServer({
schema: rootSchema, // --> this is root schema object
context: context => context,
introspection: true,
})
graphQLServer.applyMiddleware({ app, path: '/graphql' })
server.js
import http from 'http'
import express from 'express'
import apiRouter from './api' // --> the above file
const app = express()
app.use([some middlewares])
app.use('/', apiRouter)
....
....
export async function init () {
try {
const httpServer = http.createServer(app)
httpServer
.listen(PORT)
.on('error', (err) => { setTimeout(() => process.exit(1), 5000) })
} catch (err) {
setTimeout(() => process.exit(1), 5000)
}
console.log('Server started --- ', PORT)
}
export default app
index.js
require('babel-core')
require('babel-polyfill')
require = require('esm')(module/* , options */)
const server = require('./server.js') // --> the above file
server.init()
PROBLEM STATEMENT
I am using node index.js to start the app. So, the app is expecting Authorization header (JWT token) to be present all the times, even for the introspection query. But this is not what I want, I want that introspection query will be resolvable even without the token. So that anyone can see the documentation.
Please shed some light and please guide what is the best approach to do so. Happy coding :)
.startsWith('query Introspection') is insecure because any query can be named Introspection.
The better approach is to check the whole query.
First import graphql and prepare introspection query string:
const { parse, print, getIntrospectionQuery } = require('graphql');
// format introspection query same way as apollo tooling do
const introspectionQuery = print(parse(getIntrospectionQuery()));
Then in Apollo Server configuration check query:
context: ({ req }) => {
// allow introspection query
if (req.body.query === introspectionQuery) {
return {};
}
// continue
}
There's a ton of different ways to handle authorization in GraphQL, as illustrated in the docs:
Adding middleware for express (or some other framework like hapi or koa)
Checking for authorization inside individual resolvers
Checking for authorization inside your data models
Utilizing custom directives
Adding express middleware is great for preventing unauthorized access to your entire schema. If you want to allow unauthenticated access to some fields but not others, it's generally recommended you move your authorization logic from the framework layer to the GraphQL or data model layer using one of the methods above.
So finally I found the solution and here is what I did.
Let me first tell you that there were 2 middle-wares added on base path. Like this:
app //--> this is express.Router()
.use(jwtMw) // ---> these are middlewares
.use(otherMw)
The jwtMw is the one that checks the authentication of the user, and since even introspection query comes under this MW, it used to authenticate that as well. So, after some research I found this solution:
jwtMw.js
function addJWTMeta (req, res, next) {
// we can check for null OR undefined and all, then check for query Introspection, with better condition like with ignore case
if (req.body.query.trim().startsWith('query Introspection')) {
req.isIntrospection = true
return next()
}
...
...
// ---> extra code to do authentication of the USER based on the Authorization header
}
export default addJWTMeta
otherMw.js
function otherMw (req, res, next) {
if (req.isIntrospection) return next()
...
...
// ---> extra code to do some other context creation
}
export default otherMw
So here in jwtMw.js we are checking that if the query is Introspection just add a variable in req object and move forward, and in next middleware after the jwtMw.js whosoever wants to check for introspection query just check for that variable (isIntrospection, in this case) and if it is present and is true, please move on. We can add this code and scale to every middleware that if req.isIntrospection is there just carry on or do the actual processing otherwise.
Happy coding :)