How to Protect Uploads, if User is not Logged In?

I use WordPress for a private site where users upload files.
I use the “Private WordPress” to prevent access in to the site if the user is not logged in.

I would like to do the same to the files uploaded in the uploads folder.

Read More

So if a user its not logged in they wont be able to access to :
https://xxxxxxx.com/wp-content/uploads/2011/12/xxxxxxx.pdf
if they try to access but they are not logged then they should be redirected to login page for example.

I found a plugin called private files but last time updated was in 2009 and it does not seems to work on my WordPress.

Anyone know any method?
Hotlinking method will be enough to protect this?

I also found this method :

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} ^.*uploads/private/.*
RewriteCond %{HTTP_COOKIE} !^.*wordpress_logged_in.*$ [NC]
RewriteRule . /index.php [R,L]
RewriteRule ^index.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

But then any user that replicate the cookie could pass this right?
Regards

Related posts

Leave a Reply

4 comments

  1. Only checking if the cookie exists, is not much of a strict protection.

    To get a stronger protection, you can pass or “proxy” all requests to the uploaded folder (exemplary uploads in the following example) through a php script:

    RewriteCond %{REQUEST_FILENAME} -s
    RewriteRule ^wp-content/uploads/(.*)$ dl-file.php?file=$1 [QSA,L]
    

    All requests to uploaded files (which includes images in posts) would go to dl-file.php which then can do verify if the user is logged in or not.

    If the user is not logged in, your sites login-form will be shown. After the user logged in, she will get redirected back to the file and can download it now.

    Exemplary dl-file.php.

    Something similar can be found in wp-includesms-files.php in your wordpress installation, but that one is for multisite and w/o the login check and redirects.

    Depending on how much traffic you have, it could be wise to better integrate this with your server, e.g. X-Accel-Redirect or X-Sendfile headers.

  2. Two ways, simple in 2. with the help of an apache rule or in 1. with the help of custom code in a plugin.

    1. Plugin

    You can write a plugin using the init hook and the get-value $_GET[ 'file' ];. If the user has this get-value, jump in a function to check the rights for access on the files: For example, with a checkbox inside a Meta Box.

    add_action( 'init', 'fb_init' );
    function fb_init() {
        // this in a function for init-hook
        if ( '' != $_GET[ 'file' ] ) {
            fb_get_file( $_GET[ 'file' ] );
        }
    }
    

    the function fb_get_file()

    function fb_get_file( $file ) {
    
        $upload     = wp_upload_dir();
        $the_file   = $file; 
        $file       = $upload[ 'basedir' ] . '/' . $file;
        if ( !is_file( $file ) ) {
            status_header( 404 );
            die( '404 — File not found.' );
        }
        else {
            $image = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attached_file', 'value' => $the_file ) ) ) );
            if ( 0 < count( $image ) && 0 < $image[0] -> post_parent ) { // attachment found and parent available
                if ( post_password_required( $image[0] -> post_parent ) ) { // password for the post is not available
                    wp_die( get_the_password_form() );// show the password form 
                }
                $status = get_post_meta( $image[0] -> post_parent, '_inpsyde_protect_content', true );
    
                if ( 1 == $status &&  !is_user_logged_in() ) {
                    wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                    die();
                }
            }
            else {
                // not a normal attachment check for thumbnail
                $filename   = pathinfo( $the_file );
                $images     = get_posts( array( 'post_type' => 'attachment', 'meta_query' => array( array( 'key' => '_wp_attachment_metadata', 'compare' => 'LIKE', 'value' => $filename[ 'filename' ] . '.' . $filename[ 'extension' ] ) ) ) );
                if ( 0 < count( $images ) ) {
                    foreach ( $images as $SINGLEimage ) {
                        $meta = wp_get_attachment_metadata( $SINGLEimage -> ID );
                        if ( 0 < count( $meta[ 'sizes' ] ) ) {
                            $filepath   = pathinfo( $meta[ 'file' ] );
                            if ( $filepath[ 'dirname' ] == $filename[ 'dirname' ] ) {// current path of the thumbnail
                                foreach ( $meta[ 'sizes' ] as $SINGLEsize ) {
                                    if ( $filename[ 'filename' ] . '.' . $filename[ 'extension' ] == $SINGLEsize[ 'file' ] ) {
                                        if ( post_password_required( $SINGLEimage -> post_parent ) ) { // password for the post is not available
                                            wp_die( get_the_password_form() );// show the password form 
                                        }
                                        die('dD');
                                        $status = get_post_meta( $SINGLEimage -> post_parent, '_inpsyde_protect_content', true );
    
                                        if ( 1 == $status &&  !is_user_logged_in() ) {
                                            wp_redirect( wp_login_url( $upload[ 'baseurl' ] . '/' . $the_file ) );
                                            die();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        $mime       = wp_check_filetype( $file );
    
        if( false === $mime[ 'type' ] && function_exists( 'mime_content_type' ) )
            $mime[ 'type' ] = mime_content_type( $file );
    
        if( $mime[ 'type' ] )
            $mimetype = $mime[ 'type' ];
        else
            $mimetype = 'image/' . substr( $file, strrpos( $file, '.' ) + 1 );
    
        header( 'Content-type: ' . $mimetype ); // always send this
        if ( false === strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS' ) )
            header( 'Content-Length: ' . filesize( $file ) );
    
        $last_modified = gmdate( 'D, d M Y H:i:s', filemtime( $file ) );
        $etag = '"' . md5( $last_modified ) . '"';
        header( "Last-Modified: $last_modified GMT" );
        header( 'ETag: ' . $etag );
        header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + 100000000 ) . ' GMT' );
    
        // Support for Conditional GET
        $client_etag = isset( $_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes( $_SERVER['HTTP_IF_NONE_MATCH'] ) : false;
    
        if( ! isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )
            $_SERVER['HTTP_IF_MODIFIED_SINCE'] = false;
    
        $client_last_modified = trim( $_SERVER['HTTP_IF_MODIFIED_SINCE'] );
        // If string is empty, return 0. If not, attempt to parse into a timestamp
        $client_modified_timestamp = $client_last_modified ? strtotime( $client_last_modified ) : 0;
    
        // Make a timestamp for our most recent modification...
        $modified_timestamp = strtotime($last_modified);
    
        if ( ( $client_last_modified && $client_etag )
            ? ( ( $client_modified_timestamp >= $modified_timestamp) && ( $client_etag == $etag ) )
            : ( ( $client_modified_timestamp >= $modified_timestamp) || ( $client_etag == $etag ) )
            ) {
            status_header( 304 );
            exit;
        }
    
        // If we made it this far, just serve the file
        readfile( $file );
        die();
    }
    

    You can also add a custom URL for files via the hook generate_rewrite_rules

    add_filter( 'generate_rewrite_rules', 'fb_generate_rewrite_rules' );
    
    function fb_generate_rewrite_rules( $wprewrite ) {
            $upload = wp_upload_dir();
            $path = str_replace( site_url( '/' ), '', $upload[ 'baseurl' ] );
            $wprewrite -> non_wp_rules = array( $path . '/(.*)' => 'index.php?file=$1' );
            return $wprewrite;
    }
    

    2. Apache check for the Cookie

    Leave a new .htaccess file inside of the /wp-content/uploads/ directory. Or an other defined directory for the uploads.

    How it works

    Inside of the <IfModule> containers, there are three rules that do the following:

    1. Check if the request is for any file
    2. Check for the absence of a cookie that begins with wordpress_logged_in_
    3. If these conditions are met, the file request will be denied via 403 “Forbidden” response

    The trick here is step 2, then check for the absence of a cookie that begins with wordpress_logged_in_. When the user is logged in, WordPress adds a cookie to your browser that looks like:

    wordpress_logged_in_1234567890abcdefghijklmnopqrstuvwxyz
    

    Example rule with a check for file type

    # require login for media files
    <IfModule mod_rewrite.c>
        RewriteCond %{REQUEST_FILENAME} (.*)
        RewriteCond %{HTTP_COOKIE} !wordpress_logged_in_([a-zA-Z0-9_]*) [NC]
        RewriteRule .* - [F,L]
    </IfModule>
    
  3. If you would like a plugin-based approach to solving this problem, here is a reasonably good solution that I have (finally) found:

    1. Install the plugin ‘Download Monitor’, available at:
      https://wordpress.org/plugins/download-monitor/
    2. In the WordPress Dashboard, go to the new ‘Downloads’ menu item and add a new ‘Download’, as described on the plugin documentation
      website here: https://www.download-monitor.com/kb/adding-downloads/.
      Take note of the ‘Download’ shortcode provided for you (eg. save to
      Notepad). Note that the file gets saved in /wp-content/uploads/dlm_uploads/
    3. In the ‘Download options’ metabox, specify ‘Members only’ (as documented here https://www.download-monitor.com/kb/download-options/), and click ‘Publish’.
    4. On the page that you want the Members only download to appear, add in the shortcode you took note of in step #2, and ‘Publish/Update’ the page, as documented here: https://www.download-monitor.com/kb/shortcode-download/. You can change the download link template as described here https://www.download-monitor.com/kb/content-templates/, or create your own (eg. to remove the Download ‘count’)
    5. Browse to your page, you should see a download link (but which does not reveal the URL to the download file). If you browse to the same page in a new browser window (or Incognito window), you should find that the download no longer works.

    This means that anyone not logged in cannot either download the file or see the real URL to the file. If in the event that someone unauthorised figures out the URL to the file, the plugin also stops users browsing to the real file URL by blocking access to the /wp-content/uploads/dlm_uploads/ folder.

    Bonus: if you a doing this for a site where you need users to be able to login as ‘Members’ only (but have no WordPress permissions like page editing or being an Admin), install the ‘Members’ plugin https://wordpress.org/plugins/members/, create a new user role called ‘Member’, and give it the single capability of ‘read’, create a new User in WordPress, and make sure to give them a role of ‘Member’.

    If you want protect the content of pages, the ‘Members’ plugin provides some options, or there are other plugins out there. If you want to theme the login page for Members to look better than the WordPress default login form, use something like ‘Theme My Login’: https://wordpress.org/plugins/theme-my-login/