diff --git a/app/controllers/active_storage/blobs/redirect_controller.rb b/app/controllers/active_storage/blobs/redirect_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..906eade463657346c1ad3b77dcbc796398bc6d4d
--- /dev/null
+++ b/app/controllers/active_storage/blobs/redirect_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# Take a signed permanent reference for a blob and turn it into an expiring service URL for download.
+# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
+# security-through-obscurity factor of the signed blob references, you'll need to implement your own
+# authenticated redirection controller.
+class ActiveStorage::Blobs::RedirectController < ActiveStorage::BaseController
+  include ActiveStorage::CheckAbilities
+  include ActiveStorage::SetBlob
+
+  before_action :check_abilities, only: :show
+  skip_before_action :handle_two_factor_authentication
+
+  def show
+    expires_in ActiveStorage.service_urls_expire_in
+    redirect_to @blob.url(disposition: params[:disposition])
+  end
+
+end
diff --git a/app/controllers/active_storage/representations/redirect_controller.rb b/app/controllers/active_storage/representations/redirect_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a4c71baaf08571247075b9aad58ee6f43d5a9456
--- /dev/null
+++ b/app/controllers/active_storage/representations/redirect_controller.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download.
+# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the
+# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own
+# authenticated redirection controller.
+class ActiveStorage::Representations::RedirectController < ActiveStorage::Representations::BaseController
+  include ActiveStorage::CheckAbilities
+
+  before_action :check_abilities, only: :show
+  skip_before_action :handle_two_factor_authentication
+
+  def show
+    expires_in ActiveStorage.service_urls_expire_in
+    redirect_to @representation.url(disposition: params[:disposition])
+  end
+
+end
diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/app/controllers/concerns/active_storage/check_abilities.rb b/app/controllers/concerns/active_storage/check_abilities.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ea59d518d0694300f58b80a06ea371d6542baba1
--- /dev/null
+++ b/app/controllers/concerns/active_storage/check_abilities.rb
@@ -0,0 +1,10 @@
+module ActiveStorage::CheckAbilities
+  extend ActiveSupport::Concern
+
+  private
+
+  def check_abilities
+    university = University.with_host(request.host)
+    render(file: Rails.root.join('public/403.html'), formats: [:html], status: 403, layout: false) and return if university.present? && @blob.university_id != university.id
+  end
+end
diff --git a/app/models/research/journal/article.rb b/app/models/research/journal/article.rb
index 20a415c655deaf08befa1c4a77cf15c2faa94b1f..0410c664d3b6e9859bf11e853a325e9043904631 100644
--- a/app/models/research/journal/article.rb
+++ b/app/models/research/journal/article.rb
@@ -10,7 +10,7 @@
 #  text                       :text
 #  title                      :string
 #  created_at                 :datetime         not null
-#  updated_at                 :date             not null
+#  updated_at                 :datetime         not null
 #  research_journal_id        :uuid             not null
 #  research_journal_volume_id :uuid
 #  university_id              :uuid             not null
diff --git a/config/initializers/active_storage.rb b/config/initializers/active_storage.rb
new file mode 100644
index 0000000000000000000000000000000000000000..efe52fbbf55c2eb6dfa68c97ee375552c0eaab4c
--- /dev/null
+++ b/config/initializers/active_storage.rb
@@ -0,0 +1,34 @@
+require 'active_storage/attachment'
+require 'active_storage/filename'
+
+ActiveStorage::Engine.config.active_storage.content_types_to_serve_as_binary.delete('image/svg+xml')
+
+# Hook ActiveStorage::Attachment to add brand_id to attachments records
+ActiveStorage::Attachment.class_eval do
+  after_save :denormalize_university_id_for_blob
+
+  def denormalize_university_id_for_blob
+    university_id = case self.record.class.name
+    when 'University'
+        self.record.id
+      when 'ActiveStorage::VariantRecord'
+        self.record.blob.university_id
+      else
+        self.record.university_id
+    end
+
+    self.blob.update_column(:university_id, university_id)
+  end
+end
+
+# Override ActiveStorage::Filename#sanitized to remove accents and all special chars
+# Base method: https://github.com/rails/rails/blob/v6.1.3/activestorage/app/models/active_storage/filename.rb#L57
+ActiveStorage::Filename.class_eval do
+  def sanitized
+    base_filename = base.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�")
+                        .strip
+                        .tr("\u{202E}%$|:;/\t\r\n\\", "-")
+                        .parameterize(preserve_case: true)
+    [base_filename, extension_with_delimiter].join('')
+  end
+end
diff --git a/db/migrate/20211008124136_add_university_id_to_active_storage_blob.rb b/db/migrate/20211008124136_add_university_id_to_active_storage_blob.rb
new file mode 100644
index 0000000000000000000000000000000000000000..1dcdf0425a700f563c2a8c9cebfef2724a970735
--- /dev/null
+++ b/db/migrate/20211008124136_add_university_id_to_active_storage_blob.rb
@@ -0,0 +1,6 @@
+class AddUniversityIdToActiveStorageBlob < ActiveRecord::Migration[6.1]
+  def change
+    add_reference :active_storage_blobs, :university, type: :uuid, index: true
+
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ef44af74f7500f6c9f2cd55f56ce31a3ea83f5d5..a31dc6a068327c6c00930010697ab468e7635a51 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2021_10_08_090117) do
+ActiveRecord::Schema.define(version: 2021_10_08_124136) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "pgcrypto"
@@ -35,7 +35,9 @@ ActiveRecord::Schema.define(version: 2021_10_08_090117) do
     t.bigint "byte_size", null: false
     t.string "checksum", null: false
     t.datetime "created_at", null: false
+    t.uuid "university_id"
     t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+    t.index ["university_id"], name: "index_active_storage_blobs_on_university_id"
   end
 
   create_table "active_storage_variant_records", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
@@ -178,7 +180,7 @@ ActiveRecord::Schema.define(version: 2021_10_08_090117) do
     t.uuid "research_journal_id", null: false
     t.uuid "research_journal_volume_id"
     t.datetime "created_at", precision: 6, null: false
-    t.date "updated_at", null: false
+    t.datetime "updated_at", precision: 6, null: false
     t.uuid "updated_by_id"
     t.text "abstract"
     t.text "references"
diff --git a/public/403.html b/public/403.html
new file mode 100644
index 0000000000000000000000000000000000000000..edacef6a0fcef110042def4252e8ec28893fdc94
--- /dev/null
+++ b/public/403.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="fr">
+  <head>
+    <title>You do not have access to this page (403)</title>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width,initial-scale=1">
+    <style>
+
+    .error-page {
+      font-family: arial, sans-serif;
+      margin: 0;
+    }
+
+    .error-page div.dialog {
+      width: 95%;
+      max-width: 22em;
+      margin: calc(50vh - 140px) auto 0;
+    }
+
+    .error-page div.dialog span{
+      font-size: 200px;
+      font-weight: 900;
+      letter-spacing: -0.04em;
+      display: block;
+      width: 100%;
+      line-height: 0.9;
+    }
+
+    .error-page h1 {
+      font-size: 16px;
+      line-height: 1.5em;
+    }
+
+    .error-page h1 em {
+      font-weight: 400;
+    }
+
+    </style>
+  </head>
+<body class="error-page">
+  <!-- This file lives in public/500.html -->
+  <div class="dialog">
+    <span>
+      403.
+    </span>
+    <div>
+      <h1>You do not have access to this page.</h1>
+    </div>
+  </body>
+</html>
diff --git a/test/fixtures/research/journal/articles.yml b/test/fixtures/research/journal/articles.yml
index 432f9dfdfadb94b7ca93b6a486ff491bde470558..9db4f619ddc3077b9ee1726a1514bb3455aa1f21 100644
--- a/test/fixtures/research/journal/articles.yml
+++ b/test/fixtures/research/journal/articles.yml
@@ -10,7 +10,7 @@
 #  text                       :text
 #  title                      :string
 #  created_at                 :datetime         not null
-#  updated_at                 :date             not null
+#  updated_at                 :datetime         not null
 #  research_journal_id        :uuid             not null
 #  research_journal_volume_id :uuid
 #  university_id              :uuid             not null
diff --git a/test/models/research/journal/article_test.rb b/test/models/research/journal/article_test.rb
index 12249debb659d71a9fb563c9450468a466d3d66e..d1013ba1a1385e9ba42cbd8cbde079a9c2db7c8f 100644
--- a/test/models/research/journal/article_test.rb
+++ b/test/models/research/journal/article_test.rb
@@ -10,7 +10,7 @@
 #  text                       :text
 #  title                      :string
 #  created_at                 :datetime         not null
-#  updated_at                 :date             not null
+#  updated_at                 :datetime         not null
 #  research_journal_id        :uuid             not null
 #  research_journal_volume_id :uuid
 #  university_id              :uuid             not null