diff --git a/.env.dev b/.env.dev
index de58e27583f7d520e29d997ccb0bb02f55823133..a7413b0ff10436dfbb3735e9cbf66f18304db251 100644
--- a/.env.dev
+++ b/.env.dev
@@ -1,3 +1,3 @@
-BACKEND_URL=http://localhost:12081
+BACKEND_URL=http://localhost:6001
 YOUTUBE_API_KEY=
-API_AUTHENTICATION_REQUIRED=False
+API_AUTHENTICATION_REQUIRED=True
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 5ba197145330a7a29a3e9e03d7d33db9087af223..b6e195ca2acb360c0877c3163b096cf7c2459568 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -288,6 +288,7 @@ REST_FRAMEWORK = {
     'PAGE_SIZE': 25,
 
     'DEFAULT_AUTHENTICATION_CLASSES': (
+        'funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS',
         'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
         'rest_framework.authentication.SessionAuthentication',
         'rest_framework.authentication.BasicAuthentication',
diff --git a/api/funkwhale_api/common/authentication.py b/api/funkwhale_api/common/authentication.py
new file mode 100644
index 0000000000000000000000000000000000000000..b75f3b516d5d2505f40900f7314f1f70e557ecb2
--- /dev/null
+++ b/api/funkwhale_api/common/authentication.py
@@ -0,0 +1,20 @@
+from rest_framework import exceptions
+from rest_framework_jwt import authentication
+from rest_framework_jwt.settings import api_settings
+
+
+class JSONWebTokenAuthenticationQS(
+        authentication.BaseJSONWebTokenAuthentication):
+
+    www_authenticate_realm = 'api'
+
+    def get_jwt_value(self, request):
+        token = request.query_params.get('jwt')
+        if 'jwt' in request.query_params and not token:
+            msg = _('Invalid Authorization header. No credentials provided.')
+            raise exceptions.AuthenticationFailed(msg)
+        return token
+
+    def authenticate_header(self, request):
+        return '{0} realm="{1}"'.format(
+            api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)
diff --git a/api/funkwhale_api/common/tests/test_jwt_querystring.py b/api/funkwhale_api/common/tests/test_jwt_querystring.py
new file mode 100644
index 0000000000000000000000000000000000000000..90e63775d9ef7e5e2aeeaac6a144d3ff1812bd6e
--- /dev/null
+++ b/api/funkwhale_api/common/tests/test_jwt_querystring.py
@@ -0,0 +1,32 @@
+from test_plus.test import TestCase
+from rest_framework_jwt.settings import api_settings
+
+from funkwhale_api.users.models import User
+
+
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+
+class TestJWTQueryString(TestCase):
+    www_authenticate_realm = 'api'
+
+    def test_can_authenticate_using_token_param_in_url(self):
+        user = User.objects.create_superuser(
+            username='test', email='test@test.com', password='test')
+
+        url = self.reverse('api:v1:tracks-list')
+        with self.settings(API_AUTHENTICATION_REQUIRED=True):
+            response = self.client.get(url)
+
+        self.assertEqual(response.status_code, 401)
+
+        payload = jwt_payload_handler(user)
+        token = jwt_encode_handler(payload)
+        print(payload, token)
+        with self.settings(API_AUTHENTICATION_REQUIRED=True):
+            response = self.client.get(url, data={
+                'jwt': token
+            })
+
+            self.assertEqual(response.status_code, 200)
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 506db123923ce9b706a46dcbff5771ec40473e60..4a4032c57f80695ba88fb92826941be3ede23605 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -1,5 +1,7 @@
 import os
 import json
+import unicodedata
+import urllib
 from django.core.urlresolvers import reverse
 from django.db import models, transaction
 from django.db.models.functions import Length
@@ -137,8 +139,10 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet):
             return Response(status=404)
 
         response = Response()
-        response["Content-Disposition"] = "attachment; filename={0}".format(
-            f.audio_file.name)
+        filename = "filename*=UTF-8''{}{}".format(
+            urllib.parse.quote(f.track.full_name),
+            os.path.splitext(f.audio_file.name)[-1])
+        response["Content-Disposition"] = "attachment; {}".format(filename)
         response['X-Accel-Redirect'] = "{}{}".format(
             settings.PROTECT_FILES_PATH,
             f.audio_file.url)
diff --git a/front/src/audio/queue.js b/front/src/audio/queue.js
index c91c1d2acceb52cdc69daf71b95b7e693f3913ad..efa3dcdf7d6ba5ee22694b1e5d0ac160924d8c7c 100644
--- a/front/src/audio/queue.js
+++ b/front/src/audio/queue.js
@@ -5,6 +5,8 @@ import Audio from '@/audio'
 import backend from '@/audio/backend'
 import radios from '@/radios'
 import Vue from 'vue'
+import url from '@/utils/url'
+import auth from '@/auth'
 
 class Queue {
   constructor (options = {}) {
@@ -181,7 +183,17 @@ class Queue {
     if (!file) {
       return this.next()
     }
-    this.audio = new Audio(backend.absoluteUrl(file.path), {
+    let path = backend.absoluteUrl(file.path)
+
+    if (auth.user.authenticated) {
+      // we need to send the token directly in url
+      // so authentication can be checked by the backend
+      // because for audio files we cannot use the regular Authentication
+      // header
+      path = url.updateQueryString(path, 'jwt', auth.getAuthToken())
+    }
+
+    this.audio = new Audio(path, {
       preload: true,
       autoplay: true,
       rate: 1,
diff --git a/front/src/auth/index.js b/front/src/auth/index.js
index b5a3fb5adb1f4695e1c420f51880755cc2519297..219a1531f036b5eeabf78f03912dd509d3f6534e 100644
--- a/front/src/auth/index.js
+++ b/front/src/auth/index.js
@@ -50,7 +50,7 @@ export default {
 
   checkAuth () {
     logger.default.info('Checking authentication...')
-    var jwt = cache.get('token')
+    var jwt = this.getAuthToken()
     var username = cache.get('username')
     if (jwt) {
       this.user.authenticated = true
@@ -63,9 +63,13 @@ export default {
     }
   },
 
+  getAuthToken () {
+    return cache.get('token')
+  },
+
   // The object to be passed as a header for authenticated requests
   getAuthHeader () {
-    return 'JWT ' + cache.get('token')
+    return 'JWT ' + this.getAuthToken()
   },
 
   fetchProfile () {
diff --git a/front/src/components/browse/Track.vue b/front/src/components/browse/Track.vue
index 336af285b3aae63d19d1cbaa60e3b50a6e45a2f4..1e15687934c4943a5f8ec74547f75f3ce53902da 100644
--- a/front/src/components/browse/Track.vue
+++ b/front/src/components/browse/Track.vue
@@ -61,6 +61,8 @@
 
 <script>
 
+import auth from '@/auth'
+import url from '@/utils/url'
 import logger from '@/logging'
 import backend from '@/audio/backend'
 import PlayButton from '@/components/audio/PlayButton'
@@ -121,7 +123,11 @@ export default {
     },
     downloadUrl () {
       if (this.track.files.length > 0) {
-        return backend.absoluteUrl(this.track.files[0].path)
+        let u = backend.absoluteUrl(this.track.files[0].path)
+        if (auth.user.authenticated) {
+          u = url.updateQueryString(u, 'jwt', auth.getAuthToken())
+        }
+        return u
       }
     },
     lyricsSearchUrl () {
diff --git a/front/src/utils/url.js b/front/src/utils/url.js
new file mode 100644
index 0000000000000000000000000000000000000000..61a430988a4093852812d5aada507cdd14acf38b
--- /dev/null
+++ b/front/src/utils/url.js
@@ -0,0 +1,11 @@
+export default {
+  updateQueryString (uri, key, value) {
+    var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i')
+    var separator = uri.indexOf('?') !== -1 ? '&' : '?'
+    if (uri.match(re)) {
+      return uri.replace(re, '$1' + key + '=' + value + '$2')
+    } else {
+      return uri + separator + key + '=' + value
+    }
+  }
+}