Resolve Content Security Policy (CSP) Issues in Your Discord Activity Using a Node.js Proxy
Tags: discord, programming, node, javascript
Published: 2024-06-17 15:00:00

If you're building a **[Discord Activity](https://support.discord.com/hc/en-us/articles/4422142836759-Activities-on-Discord)**, you may encounter issues with **[Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)** restrictions.

**CSP** is a security feature that helps prevent **[cross-site scripting attacks](https://owasp.org/www-community/attacks/xss)** by restricting the resources a web page can load. However, it can sometimes interfere with loading external resources like fonts or media in your activity.

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h4rr7xajb7ggu14gnlpw.png)

The recommended fix is to use Discord's **[URL Mapping](https://discord.com/developers/applications)** feature to rewrite URLs. However, this method has limitations and may not work in all cases.

## Proxies to the Rescue

An alternative solution is to use a **[proxy server](https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling)** to fetch external resources and serve them from your server. This way, you can bypass **CSP** restrictions and load resources without any issues.

The way it works is simple:

1. Your activity makes a request to your server for an external resource.
2. Your server fetches the resource and streams it back as a response.
3. Your activity receives the resource from your server, bypassing **CSP** restrictions.

This method allows you to load any content from any source without having to map each domain individually in Discord's **URL Mapping**. It may sound complex, but it's **[literally one line of code](#create-your-own-proxy)** if you created your **Discord Activity** with **[Robo.js](https://roboplay.dev/docs)**.

## Create a Discord Activity Project

**[Create a Discord Activity with Robo.js](https://dev.to/waveplay/how-to-build-a-discord-activity-easily-with-robojs-5bng)** if you don't have one already:

```bash
npx create-robo my-activity -k activity
```

We'll be using **[React](https://react.dev)** for this example, but you can apply the same principles to any frontend framework or vanilla **[HTML](https://developer.mozilla.org/en-US/docs/Web/HTML)** and **[CSS](https://developer.mozilla.org/en-US/docs/Web/CSS)**.

We highly recommend using **[create-robo](https://docs.roboplay.dev/cli/create-robo)** to create a **Discord Activity** project. **[Robo.js](https://roboplay.dev/docs)** providers a lot of features and tools to help you build your activity faster and more efficiently, such as **[Multiplayer Support](https://dev.to/waveplay/how-to-add-multiplayer-to-your-discord-activity-lo1)**, **[Easy Hosting](https://docs.roboplay.dev/discord-activities/hosting)**, **[Streamlined Tunnels](https://docs.roboplay.dev/discord-activities/tunnels)**, **[Built-in Database](https://docs.roboplay.dev/robojs/flashcore)**, **[Plugin System](https://docs.roboplay.dev/plugins/overview)**, and so much more!

{% embed https://dev.to/waveplay/how-to-build-a-discord-activity-easily-with-robojs-5bng %}

## Making a Music Player

Let's say you want to add an audio player to your activity that plays music from an external source. You might run into **CSP** issues when trying to load the audio file due to restrictions on loading external resources.

Let's create a simple music player using the `<audio>` element in **React** by adding a new file named `Player.tsx` inside the `/src/app` folder:

```jsx
import { useState, useRef } from 'react'

export const Player = (props) => {
	const { url } = props
	const audioRef = useRef(null)
	const [isPlaying, setIsPlaying] = useState(false)

	const togglePlayPause = () => {
		const audio = audioRef.current

		if (audio && isPlaying) {
			audio.pause()
		} else if (audio && !isPlaying) {
			audio.play()
		}
		setIsPlaying(!isPlaying)
	}

	return (
		<>
			<audio ref={audioRef} src={url} preload="auto" />
			<button onClick={togglePlayPause}>{isPlaying ? 'Pause' : 'Play'}</button>
		</>
	)
}
```

Then update your `Activity.tsx` file to use it:

```jsx
 import { Player } from './Player'

const ExternalUrl = 'https://media.waveplay.com/t/ckwldfuiq6608re6x8dzc5tyt.mp3'

export const Activity = () => {
	return (
		<div>
			<img src="/rocket.png" className="logo" alt="Discord" />
			<h1>Hello, World</h1>
			<Player url={ExternalUrl} />
			<p>
				<small>
					Powered by{' '}
					<a className="robojs" href="https://roboplay.dev/docs">
						Robo.js
					</a>
				</small>
			</p>
		</div>
	)
}
```

In this example, we're loading an audio file from an external source and playing it using the `<audio>` element. However, this also triggers a **CSP** violation because `https://media.waveplay.com` is not whitelisted in **[Discord's Proxy](https://docs.roboplay.dev/discord-activities/proxy)**.

Let's fix this by creating a proxy!

## Create Your Own Proxy

Create a new file named `proxy.js` inside the `/src/api` folder:

```js
export default async (request) => {
	return fetch(request.query.url)
}
```

_That's it!_ Modify as you see fit to improve security.

The **[@robojs/server](https://docs.roboplay.dev/plugins/server)** plugin used in this **Robo.js** project extends the standard web **[Request](https://developer.mozilla.org/docs/Web/API/Request)** and **[Response](https://developer.mozilla.org/docs/Web/API/Response)** interfaces which work seamlessly with **[Fetch API](https://developer.mozilla.org/docs/Web/API/Fetch_API)**, which is why we can return the result directly.

> **Heads up!** You'll need at least **@robojs/server** version `0.5.3` or newer.

Now, update your `Activity.tsx` file to use the proxy:

```jsx
<Player url={'/api/proxy?url=' + ExternalUrl} />
```

By adding the `/api/proxy?url=` prefix to the audio URL, we're telling our server to fetch the audio file and serve it back to the activity without triggering **CSP** violations.

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3obwnrg2yk3jkupu8jy.png)

You can use this method to proxy simple external resources in your activity. For more complex scenarios, such as web pages, our upcoming **@robojs/browser** plugin will provide a more robust solution.

## Security Implications

While this method is effective for bypassing **CSP** restrictions, it does introduce additional latency because your server has to fetch the resource before serving it back to the activity. This may impact the performance of your activity, especially for large files or high traffic, so make sure your **[Hosting Service](https://docs.roboplay.dev/discord-activities/hosting)** can handle it.

Be aware of potential security risks when using a proxy server, such as **[URL Injections](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html)** and **[SSRF (Server-Side Request Forgery)](https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_(SSRF))** attacks. Always use Discord's **URL Mapping** whenever possible and only resort to proxies when necessary.

## Easy, Right?

By creating a simple proxy server, you can bypass **CSP** restrictions and load external resources in your **Discord Activity** without any issues. This method is easy and effective, allowing you to focus on building your activity without worrying about security restrictions. You can find the complete source code for this example available in **TypeScript** as a **[Robo.js Template](https://docs.roboplay.dev/templates/overview)**.

➞ [🔗 **Template:** Music Player Proxy](https://github.com/Wave-Play/robo.js/tree/main/templates/discord-activities/music-player-proxy)

Don't forget to **[join our Discord server](https://roboplay.dev/discord)** to chat with other developers, ask questions, and share your projects. We're here to help you build amazing apps with **Robo.js**! 🚀

{% embed https://roboplay.dev/discord %}

Our very own Robo, **Sage**, is there to answer any questions about Robo.js, Discord.js, and more!