ColdBox HMVC Documentation
DocsSourceSupportTraining
6.x
6.x
  • Introduction
  • Intro
    • Release History
      • What's New With 6.11.0
      • What's New With 6.10.0
      • What's New With 6.9.0
      • What's New With 6.8.2
      • What's New With 6.8.1
      • What's New With 6.8.0
      • What's New With 6.7.0
      • What's New With 6.6.1
      • What's New With 6.6.0
      • What's New With 6.5.x
      • What's New With 6.4.0
      • What's New With 6.3.0
      • What's New With 6.2.x
      • What's New With 6.1.0
      • What's New With 6.0.0
      • Upgrading to ColdBox 6
    • About This Book
    • Author
  • For Newbies
    • 60 Minute Quick Start
      • Installing ColdBox
      • My First ColdBox Application
      • My First Handler & View
      • Linking Events Together
      • Working With Event Handlers
      • Adding A Layout
      • Adding A Model
      • RESTFul Data
      • Next Steps
  • Getting Started
    • Getting Started Guide
    • Installation
    • Conventions
    • Configuration
      • ColdBox.cfc
        • Configuration Directives
          • CacheBox
          • ColdBox
          • Conventions
          • Environments
          • Flash
          • InterceptorSettings
          • Interceptors
          • Layouts
          • LayoutSettings
          • LogBox
          • Modules
          • ModuleSettings
          • Settings
          • WireBox
        • System Settings (Java Properties and Environment Variables)
      • Using Settings
      • Bootstrapper - Application.cfc
  • The Basics
    • Request Context
    • Routing
      • Requirements
        • Rewrite Rules
      • Application Router
      • Routing DSL
        • Routing By Convention
        • Pattern Placeholders
        • Routing Methods
        • Resourceful Routes
        • Named Routes
        • Routing Groups
        • Routing Namespaces
      • Building Routable Links
      • RESTFul Extension Detection
      • HTTP Method Spoofing
      • HTML Base Tag
      • Pathinfo Providers
    • Event Handlers
      • How are events called?
      • Getting & Setting Values
      • Setting Views
      • Relocating
      • Rendering Data
      • Sending Files
      • Interception Methods
        • Pre Advices
        • Post Advices
        • Around Advices
      • Model Integration
        • Model Data Binding
      • HTTP Method Security
      • Implicit Methods
      • Executing Events
      • Executing Routes
      • Viewlets - Reusable Events
      • Event Caching
      • Validation
    • Layouts & Views
      • Views
        • Rendering Views
        • Rendering External Views
        • Rendering With Local Variables
        • Rendering Collections
        • View Caching
        • View Helpers
        • View Events
      • Layouts
        • Basic Layouts
        • Default Layout
        • Nested Layouts
        • Overriding Layouts
        • Layouts From A Module
        • Layout Helpers
        • Layout Events
      • Implicit Layout-View Declarations
      • Helpers UDF's
      • ColdBox Elixir
    • Models
      • Domain Modeling
        • Service Layer
        • Data Layers
        • Book
      • Conventions Location
      • WireBox Binder
      • Super Type Usage Methods
      • Injection DSL
        • ColdBox Namespace
        • CacheBox Namespace
        • EntityService Namespace
        • Executor Namespace
        • Java Namespace
        • LogBox Namespace
        • Models Namespace
        • Provider Namespace
        • WireBox Namespace
      • Object Scopes
      • Coding: Solo Style
        • Datasource
        • Contact.cfc
        • ContactDAO.cfc
        • ContactService.cfc
        • Contacts Handler
      • Coding: ActiveEntity Style
        • ORM
        • Contact.cfc
        • Contacts Handler
        • Views
      • Coding: Virtual Service Layer
        • ORM
        • Contacts.cfc
        • Contacts Handler
        • Views
      • Coding: ORM Scaffolding
        • ORM
        • Contacts.cfc
        • Scaffold
    • Interceptors
      • How do they work?
        • Conventions
      • Interceptor Declaration
      • Interceptor Registration
      • Dynamic Registration
      • Core Interception Points
        • Application Life Cycle Events
        • Object Creating Events
        • Layout-View Events
        • Module Events
        • CacheBox Events
      • Restricting Execution
      • Interceptor Output Buffer
      • Custom Events
        • Configuration Registration
        • Programmatic Registration
        • Listening
        • Announcing Interceptions
      • Unregistering Interceptors
      • Reporting Methods
      • Interceptor Asynchronicity
        • Async Announcements
        • Async Listeners With Join
        • Async Listeners No Join
        • Asynchronous Annotations
  • HMVC
    • Modules
      • Core Modules
      • Locations
      • Parent Configuration
      • Module Layout
        • Changing The Module Layout
      • Module Service
        • Module Lifecycle
        • Module Registration
        • Module Activation
        • Module Unloading
        • Common Methods
        • Loading New Modules
        • Loading A-la-carte Modules
        • Module Events
      • ModuleConfig
        • Public Module Properties
        • The Decorated Variables
        • The configure() Method
        • Module Settings
        • Environment Control
        • Interceptor Events
      • Module Event Executions
      • URL Routing
        • Default Route Execution
        • Module Routes Files
      • Module Async Scheduling
      • Request Context Module Methods
      • Layout and View Renderings
        • Layout/View Discovery
        • Overriding Views
        • Overriding Layouts
        • Default Module Layout
        • Explicit Module Renderings
      • Models
      • Module CF Mappings
      • Module Dependencies
      • Module Helpers
      • Module Bundles
      • Module Inception
  • Testing
    • Testing Quick Start
    • Testing ColdBox Applications
      • Test Harness
      • ColdBox Testing Classes
      • Common Testing Methods
      • Integration Testing
        • Life-Cycle Events
        • Request Setup()
        • The execute() Method
        • HTTP Testing Methods
        • Testing Without Virtual Application
        • Test Annotations
      • Interceptor Testing
      • Model Object Testing
      • Tips & Tricks
  • Digging Deeper
    • Async Programming
      • Async Pipelines & Futures
      • Parallel Computations
      • Executors
      • Scheduled Tasks
    • ColdBox Proxy
      • Getting Started
      • The Base Proxy Object
      • The Event Handlers
        • Distinguishing Request Types
        • RenderData()
      • Proxy Events
      • Standard Return Types
      • Caveats & Gotchas
    • Controller Decorator
    • Flash RAM
      • Flash Storage
      • Using Flash RAM
      • Creating Your Own Flash Scope
    • HTML Helper
    • REST Handler
    • Request Context Decorator
    • Recipes
      • Building REST APIs
      • Application Templates
      • ColdBox Exception Handling
      • Debugging ColdBox Apps
      • Clearing the View Cache
      • Basic HTTP Authentication Interceptor
    • Scheduled Tasks
  • Architecture Concepts
    • What is MVC
    • What is ColdBox
    • How ColdBox Works
    • Testing Concepts
      • Functional Testing
      • Non-Functional Testing
      • Bugs Cost Money
      • Static Testing
      • Dynamic Testing
      • Developer Focus
      • Testing Vocabulary
Powered by GitBook
On this page
  • Introduction
  • Application Setup
  • Security Service
  • Security Service Test
  • Security Service
  • The Interceptor
  • Interceptor Test
  • Interceptor Code
  • Interceptor Declaration
  • Why provider
  • Extra Credit

Was this helpful?

Edit on GitHub
Export as PDF
  1. Digging Deeper
  2. Recipes

Basic HTTP Authentication Interceptor

Last updated 4 years ago

Was this helpful?

Introduction

In this recipe we will create a simple interceptor that will be in charge of challenging users with HTTP Basic Authentication. It features the usage of all the new RESTful methods in our Request Context that will make this interceptor really straightforward. We will start by knowing that this interceptor will need a security service to verify security, so we will also touch on this.

You can find the code for this recipe here:

Application Setup

Let's start building the app using CommandBox and installing all the necessary dependencies:

# create our folder
mkdir simple-auth --cd
# generate a coldbox app
coldbox create app "SimpleAuth"
# Install extra dependencies
install cbStorages
# Startup the server
server start

Use cbtemplate-advanced-script@6.0.0-snapshot if using ColdBox Pre-Releases

Security Service

Let's build a simple security service to track users. Use CommandBox to generate the service model with two functions and let's mark it as a singleton:

coldbox create model SecurityService authorize,isLoggedIn singleton

This will create the models/SecurityService and the companion unit tests. Let's fill them out:

Security Service Test

/**
* The base model test case will use the 'model' annotation as the instantiation path
* and then create it, prepare it for mocking and then place it in the variables scope as 'model'. It is your
* responsibility to update the model annotation instantiation path and init your model.
*/
component extends="coldbox.system.testing.BaseModelTest" model="models.SecurityService"{

    /*********************************** LIFE CYCLE Methods ***********************************/

    function beforeAll(){
        super.beforeAll();

        // setup the model
        super.setup();

        mockSession = createMock( "modules.cbstorages.models.SessionStorage" )
    }

    function afterAll(){
        super.afterAll();
    }

    /*********************************** BDD SUITES ***********************************/

    function run(){

        describe( "SecurityService Suite", function(){

            beforeEach(function( currentSpec ){
                model.init();
                model.setSessionStorage( mockSession );
            });

            it( "can be created (canary)", function(){
                expect( model ).toBeComponent();
            });

            it( "should authorize valid credentials", function(){
                mockSession.$( "set", mockSession );
                expect( model.authorize( "luis", "coldbox" ) ).toBeTrue();
            });

            it( "should not authorize invalid credentials ", function(){
                expect( model.authorize( "test", "test" ) ).toBeFalse();
            });

            it( "should verify if you are logged in", function(){
                mockSession.$( "get", true );
                expect( model.isLoggedIn() ).toBeTrue();
            });

            it( "should verify if you are NOT logged in", function(){
                mockSession.$( "get", false );
                expect( model.isLoggedIn() ).toBeFalse();
            });

        });

    }

}

Security Service

component accessors="true" singleton{

    // Dependencies
    property name="sessionStorage" inject="SessionStorage@cbStorages";

    /**
     * Constructor
     */
    function init(){
            // Mock Security For Now
        variables.username = "luis";
        variables.password = "coldbox";

        return this;
    }

    /**
     * Authorize with basic auth
     */
    function authorize( username, password ){

        // Validate Credentials, we can do better here
        if( variables.username eq username AND variables.password eq password ){
            // Set simple validation
            sessionStorage.set( "userAuthorized", true );
            return true;
        }

        return false;
    }

    /**
     * Checks if user already logged in or not.
     */
    function isLoggedIn(){
            return sessionStorage.get( "userAuthorized", "false" );
    }

}

Please note that we are using a hard coded username and password, but you can connect this to any provider or db.

The Interceptor

Let's generate the interceptor now and listen to preProcess

coldbox create interceptor name="SimpleSecurity" points="preProcess"

The preProcesslistener is to listen to all incoming requests are inspected for security. Please note that the unit test for this interceptor is also generated. Let's fill out the interceptor test first:

Interceptor Test

So to make sure this works, here is our Interceptor Test Case with all possibilities for our Security Interceptor.

component extends="coldbox.system.testing.BaseInterceptorTest" interceptor="interceptors.SimpleSecurity" {

    /*********************************** LIFE CYCLE Methods ***********************************/

    function beforeAll() {
        super.beforeAll();

        // mock security service
        mockSecurityService = createEmptyMock( "models.SecurityService" );
    }

    function afterAll() {
        super.afterAll();
    }

    /*********************************** BDD SUITES ***********************************/

    function run() {
        describe( "SimpleSecurity Interceptor Suite", function() {
            beforeEach( function( currentSpec ) {
                // Setup the interceptor target
                super.setup();
                // inject mock into interceptor
                interceptor.$property(
                    "securityService",
                    "variables",
                    mockSecurityService
                );
                mockEvent = getMockRequestContext();
            } );

            it( "can be created (canary)", function() {
                expect( interceptor ).toBeComponent();
            } );

            it( "can allow already logged in users", function() {
                // test already logged in and mock authorize so we can see if it was called
                mockSecurityService.$( "isLoggedIn", true ).$( "authorize", false );
                // call interceptor
                interceptor.preProcess( mockEvent, {} );
                // verify nothing called
                expect( mockSecurityService.$never( "authorize" ) ).toBeTrue();
            } );

            it( "will challenge if you are not logged in and you don't have the right credentials", function() {
                // test NOT logged in and NO credentials, so just challenge
                mockSecurityService.$( "isLoggedIn", false ).$( "authorize", false );
                // mock incoming headers and no auth credentials
                mockEvent
                    .$( "getHTTPHeader" )
                    .$args( "Authorization" )
                    .$results( "" )
                    .$( "getHTTPBasicCredentials", { username : "", password : "" } )
                    .$( "setHTTPHeader" );
                // call interceptor
                interceptor.preProcess( mockEvent, {} );
                // verify authorize called once
                expect( mockSecurityService.$once( "authorize" ) ).toBeTrue();
                // Assert Set Header
                expect( mockEvent.$once( "setHTTPHeader" ) ).toBeTrue();
                // assert renderdata
                expect( mockEvent.getRenderData().statusCode ).toBe( 401 );
            } );

            it( "should authorize if you are not logged in but have valid credentials", function() {
                // Test NOT logged in With basic credentials that are valid
                mockSecurityService.$( "isLoggedIn", false ).$( "authorize", true );
                // reset mocks for testing
                mockEvent
                    .$( "getHTTPBasicCredentials", { username : "luis", password : "luis" } )
                    .$( "setHTTPHeader" );
                // call interceptor
                interceptor.preProcess( mockEvent, {} );
                // Assert header never called.
                expect( mockEvent.$never( "setHTTPHeader" ) ).toBeTrue();
            } );
        } );
    }

}

As you can see from our A,B, anc C tests that we use MockBox to mock the security service, the request context and methods so we can build our interceptor without knowledge of other parts.

Interceptor Code

interceptors/SimpleSecurity.cfc
/**
 * Intercepts with HTTP Basic Authentication
 */
component {

    // Security Service
    property name="securityService" inject="provider:SecurityService";

    void function configure(){}

    void function preProcess( event, interceptData, rc, prc ){

        // Verify Incoming Headers to see if we are authorizing already or we are already Authorized
        if( !securityService.isLoggedIn() OR len( event.getHTTPHeader( "Authorization", "" ) ) ){

            // Verify incoming authorization
            var credentials = event.getHTTPBasicCredentials();
            if( securityService.authorize(credentials.username, credentials.password) ){
                // we are secured woot woot!
                return;
            };

            // Not secure!
            event.setHTTPHeader(
                name     = "WWW-Authenticate",
                value     = "basic realm=""Please enter your username and password for our Cool App!"""
            );

            // secured content data and skip event execution
            event
                .renderData(
                    data         = "<h1>Unathorized Access<p>Content Requires Authentication</p>",
                    statusCode     = "401",
                    statusText     = "Unauthorized"
                )
                .noExecution();
        }

    }

}

As you can see it relies on a SecurityService model object that is being wired via:

// Security Service
property name="securityService" inject="provider:SecurityService";

Then we check if a user is logged in or not and if not we either verify their incoming HTTP basic credentials or if none, we challenge them by setting up some cool headers and bypass event execution:

// Verify Incoming Headers to see if we are authorizing already or we are already Authorized
if( !securityService.isLoggedIn() OR len( event.getHTTPHeader("Authorization","") ) ){

    // Verify incoming authorization
    var credentials = event.getHTTPBasicCredentials();
    if( securityService.authorize(credentials.username, credentials.password) ){
        // we are secured woot woot!
        return;
    };

    // Not secure!
    event.setHTTPHeader(name="WWW-Authenticate",value="basic realm=""Please enter your username and password for our Cool App!""");

    // secured content data and skip event execution
    event.renderData(data="<h1>Unathorized Access<p>Content Requires Authentication</p>",statusCode="401",statusText="Unauthorized")
        .noExecution();
}

The renderData() is essential in not only setting the 401 status codes but also concatenating to a noExecution() method so it bypasses any event as we want to secure them.

Interceptor Declaration

Open your Coldbox.cfc configuration file and add it.

//Register interceptors as an array, we need order
interceptors = [
    // Security
    { class="interceptors.SimpleSecurity" }
];

Now reinit your app coldbox reinit and you are simple auth secured!

Why provider

You might have noticed the injection of the security service into the interceptor used a provider: DSL prefix, why? Well, global interceptors are created first, then modules are loaded, so if we don't use a provider to delay the injection, then the storages module might not be loaded yet.

Extra Credit

Now that the hard part is done, we encourage you to try and build the integration test for the application now. Please note that most likely you would NOT do the unit test for the interceptor if you do the integration portion in BDD Style.

https://github.com/coldbox-samples/simple-auth