Laravel and CAS, the finale to my auth journey

Quick Background

I’m sure most users won’t need to setup CAS, but I work for a college and we use CAS everywhere.  This means, that our custom applications have to as well.

I started this journey yesterday, and was able to get CAS setup and working pretty quickly, however it was only after some struggle I was able to crack the code on how to access it from a Blade template.  This may be pretty easy for a veteran to recognize how to do, but a veteran I am not, as you can see from my tweets and previous posts.  

In typical fashion, I’m blogging what I did, mostly as reference for me, for next time, but also in the hope that it will help some other newb… non-veteran.

I did what I normally do when looking for something like this, start with google, and usually end up on Packagist, or something.  I found an older CAS package, that said it worked with 5.x, but I was a bit worried it may not like 5.5, so I kept looking and found one by Subfission It specifically mentions 5.5, it has a wiki, and seemingly good instructions.  I created a feature branch for my app (I use GitFlow) and set it up.

The Install

First – you don’t need to do this, other than to get some extra blade template code from it) but I installed the normal Laravel Auth stuff (here’s a link to the docs if you need help with that).  This created a nice header in the blade template that I hacked up for my app.  Again, you don’t have to do this, it was just part of my auth-journey and migration.

The installation instructions are close to perfect, although it does mention adding the ServiceProvider and Facade lines to app.php, which isn’t necessary for 5.5 – you can skip those little sections.  You do have to add a couple lines to Kernal.php, a simple copy/paste job and that’s done. 

protected$routeMiddleware=[
  'auth'=>\Illuminate\Auth\Middleware\Authenticate::class,
  'auth.basic'=>\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
  'bindings'=>\Illuminate\Routing\Middleware\SubstituteBindings::class,
  'can'=>\Illuminate\Auth\Middleware\Authorize::class,
  'guest'=>\App\Http\Middleware\RedirectIfAuthenticated::class,
  'throttle'=>\Illuminate\Routing\Middleware\ThrottleRequests::class,
  'cas.auth'=>'Subfission\Cas\Middleware\CASAuth',
  'cas.guest'=>'Subfission\Cas\Middleware\RedirectCASAuthenticated',
];

I left the old auth settings in there, but these could be removed if you want them to.  Actually the probably should be because otherwise you can still back-door the old auth login, registration, etc.

After editing Kernel.php, you then run 

php artisan vendor:publish

as you would expect.  Publishing creates a config/cas.php file that you’ll have to edit and add your network’s CAS settings to.  We have a CAS server here, and I’ve setup many phpCAS applications using classic PHP, so these were a no-brainer for me.  Check with your network team, or whomever manages your CAS server for these if you don’t have them. For ours here, I only had to edit the lines that referenced the host name, there were 3 : cas_hostname, cas_real_hosts, cas_logout_url, I left the other settings as default, and they work perfectly.

You’re (hopefully) golden. Now what do you do?

First, you’ll need to require CAS authentication in your controller method.  There may be better ways to do this, and if I find one, I’ll update this post – and probably tweet it out, but I added this __construct to the top of my MainController

public function__construct()
{
    $this->middleware('cas.auth');
}

This tells the controller all methods in it requires our cas authentication. 

I created an index method in MainController like the one below. I was testing and I sent over “user”. That can be accessed via cas()->user() in the controller and that will be the username of the authenticated user.  I ended up not using that because as we’ll see below I used a different method to nab the username. For the record, you can use all of these methods in your controller if you need to.

public function index (Request $request)
{
  return view('index',[
    'user'=>cas()->user()
  ]);
}

I guess we’ll need a quick blade template to display something to prove this works.  Create an index.blade.php file and toss this in for a quick-n-dirty test:

Hello {{ user }} !

Next, set up a route to test this out and cross your fingers.

Route::get('/', 'MainController@index')->name('home');

If you have it setup correctly, when you hit this path, it’ll pop to your CAS login.  This is nice, because as a developer, I don’t have to design a login page now 🙂 

One note here, I had to email our network team to have my local domain added to the CAS server configuration because I was getting a “this application is not authorized to use this service”.  Once they added my Homestead’s domain name – portal.app I was all set.

Once you log in, it’ll pop back to your view with you logged in and it should display

Hello jwheat ! (...or whomever you logged in as)

Congrats! You’re done! It Works!

Taking it a bit further

Here’s where I used the default auth blade code.

When initially setting up Laravel’s auth, it creates some blade templates with various logged in checks.  Take a look here to see what the default “if” statement looks like.  I’ve bolded the bits below that we care about right now

@if (Auth::guest())
    <li class="nav-item"><a href="{{ route('login') }}" class="nav-link">Login</a></li>
    <li class="nav-item"><a href="{{ route('register') }}" class="nav-link">Register</a></li>
@else
    <li class="nav-item dropdown">
        <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" data-toggle="dropdown"
           aria-haspopup="true" aria-expanded="false">
            {{ Auth::user()->name }}
        </a>
        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
            <a href="{{ route('logout') }}" class="dropdown-item"
               onclick="event.preventDefault();document.getElementById('logout-form').submit();">
                Logout
            </a>

            <form id="logout-form" action="{{ route('logout') }}" method="POST"
                  style="display: none;">
                {{ csrf_field() }}
            </form>
        </div>
    </li>
@endif

Looking through that, you can see the template check to see if the user is a guest, to display the login and register links, if they’re not a guest, meaning they’re logged in, it will display their name.

With our new CAS setup, this can be changed to achieve the same results.  I have to admit it took me a bit to figure out, but once I did, it made complete sense.  There are no google results that will tell you how to check this in a blade template, so this could be THE. ONLY. PLACE. on the internet telling you 🙂  Ok, this is where the non-veteran thing comes in because its possible this is child’s play, but here it is anyway

@if ( !Cas::isAuthenticated() )

<li class="nav-item"><a href="{{ route('login') }}" class="nav-link">Login</a></li>
<li class="nav-item"><a href="{{ route('register') }}" class="nav-link">Register</a></li>
@else
<li class="nav-item dropdown">
    <a href="#" class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" data-toggle="dropdown"
       aria-haspopup="true" aria-expanded="false">
        {{ Cas::user() }}
    </a>
    <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdownMenuLink">
        <a href="{{ route('logout') }}" class="dropdown-item"
           onclick="event.preventDefault();document.getElementById('logout-form').submit();">
            Logout
        </a>

        <form id="logout-form" action="{{ route('logout') }}" method="POST"
              style="display: none;">
            {{ csrf_field() }}
        </form>
    </div>
</li>
@endif

With this CAS package, there isn’t a guest option, you’re either authenticated or not by checking – Cas::isAuthenticated() .  So I’ve kept the logic the same with the “guest” check at the top, and just tossed in a ! (not) in front.

I also need to change the routes listed in the code since the ones listed are part of the normal Auth package.  In this implementation having a register link is silly because user creation is handled by a much larger external process/system.  The login link doesn’t make sense because the entire view itself forces you to login immediately.  I guess if this were a public page with a link to login, you’d simply route to the first view in your secured app and you’d get the login page.  The Logout link can simply change to Cas::logout() Done.

What is cool here (remember me? non-veteran) is that all the methods listed on this page are all accessible in blade using Cas:: in front instead of cas()->.  That was the secret sauce I needed, the nut to crack if you will.  There is even one for Dynamic calls allowing you to call other phpCAS functions not built into this package.

Some caveats with CAS

CAS only gives you a username coming back from an external system.  If you need a local user profile on your system and collect other information like an Avitar or preferences, you’ll have to handle that yourself.  This app I’m building requires much more information, roles, etc so this is clearly just a jumping off point for me.  I’m expecting my next post will be how to create a new user locally after that first CAS login.

So there ya go, you can implement CAS into your Laravel applications now and if you work for an institution that uses CAS, you’ll be a hero.

Until my next #LessonLearned, enjoy.

This entry was posted in Laravel, LessonsLearned. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *