riksi Start a project

Dev log 07 DevPHP

Redirect logged-out users in PHP and WordPress

Updated 3 min read By

You have a page that only signed-in users should see. Check the login state before any output. If the visitor isn’t signed in, send a Location header and stop the script with exit.

In plain PHP, that check reads the session. In WordPress, use is_user_logged_in() on the template_redirect hook, or just call auth_redirect().

Plain PHP: sessions

When the user logs in, start a session, give it a fresh ID, and store who they are.

<?php
// login.php, after checking the password with password_verify()
session_start();
session_regenerate_id( true ); // new session ID, so an old one can't be reused
$_SESSION['user_id'] = $user['id'];
header( 'Location: /account/' );
exit;

Then put the same guard at the top of every protected page, before any HTML is sent.

<?php
// require_login.php
session_start();

if ( empty( $_SESSION['user_id'] ) ) {
	$back = urlencode( $_SERVER['REQUEST_URI'] );
	header( 'Location: /login.php?redirect=' . $back, true, 302 );
	exit; // without this, the rest of the page still runs and is sent
}

Include it on each page with require __DIR__ . '/require_login.php';. Three details matter here. I’d check each one on every protected page.

  • Always exit after the redirect. header() only adds a header. PHP carries on and sends the protected content in the body, and anyone can read it with curl. This is the most important point.
  • Nothing may be output before header(), not even a blank line before <?php. If it is, you get “headers already sent”.
  • Only redirect back to your own pages. After login, check that redirect starts with a single /, and not // or http. Otherwise you have an open redirect. That’s a link on your domain that can send people to any site, and phishing scams abuse them.

A redirect without an exit is only a polite request. The browser is asked to leave, but PHP keeps sending the page. To see what PHP sends, check the redirect and headers with curl.

If you check $_SESSION['loggedin'] != 1 without first testing that the key exists, PHP 8 raises a warning. empty() handles both cases.

WordPress: the quick way

WordPress has a function for exactly this. auth_redirect() checks the login cookie. If there is none, it redirects to the login screen with the current page as the return address. Then it exits. For a simple members page, it’s what I’d use.

add_action( 'template_redirect', function () {
	if ( is_page( 'members' ) ) {
		auth_redirect();
	}
} );

template_redirect runs after WordPress knows which page is being viewed. It runs before the template sends any output. That makes it the right moment for redirects. It’s like a ticket check at a theatre door. Staff know which show you’re there for, but nobody is in a seat yet.

WordPress: your own rules

Sometimes you need more control. For example, you may want a custom login page, to protect a whole post type, or to check a role. Then write the condition yourself. I prefer wp_safe_redirect() here, because it only allows redirects to your own site.

add_action( 'template_redirect', function () {
	$protected = is_page( array( 'members', 'downloads' ) ) || is_singular( 'course' );

	if ( $protected && ! is_user_logged_in() ) {
		wp_safe_redirect( wp_login_url( get_permalink() ) );
		exit;
	}

	// Signed in, but the wrong kind of account.
	if ( is_singular( 'course' ) && ! current_user_can( 'read_private_posts' ) ) {
		wp_safe_redirect( home_url( '/upgrade/' ) );
		exit;
	}
} );

wp_login_url( $redirect ) builds the login link with a return address. So people land back on the page they wanted after they sign in. It’s a small detail, I know. But nobody likes to sign in and then search for the page a second time.

Protect the other ways in

A redirect protects the page template. But people can often reach the same content another way. Check the REST API (/wp-json/wp/v2/pages), RSS feeds, search results and the files themselves.

Media uploads are plain files served by the web server. So a PDF linked from a members page is public to anyone with the URL, unless you protect the folder at server level. For those files, a membership plugin or a server rule does the job better than a redirect.

My advice is to test the protection while logged out, in a private window. Try the page, the feed and the REST API, not only the template. If a private window can read it, so can everyone else.

Filed under DevPHP
Tagged
Share:

Comments

No comments yet. Questions, fixes and better ways are all welcome.

Leave a comment

Your email is never shown. Comments are checked before they appear, so yours may take a little while.

Start a project

Tell us what is
not working.

A few lines is enough. A real person reads every message and replies by email. Or choose the way that suits you.