Search code examples
angularsession-cookiescoldfusion-11session-management

How do I use ColdFusion Session Management with a Single Page Application?


I have an Angular 4 SPA (single page application) being served by a server that has ColdFusion 11 on it. I'm using, via AJAX calls, many functions contained in .CFC files on that ColdFusion server.

I want the following to happen:

The user goes to my Angular 4 app's page (myapp.mydomain.com) and will be redirected to the login screen (myapp.mydomain.com/login) wherein they will enter their username and password. The Angular 4 app will then call a .CFC on the server to validate their login info. The .CFC will return a "yes" or "no" validating the info. Then the Angular 4 app redirects them to myapp.mydomain.com/home (or wherever I want them to go).

At the same time, I want ColdFusion to create a new session for this user -- so that, if the session times out, or the user logs off, any further calls to any other .CFCs will be rejected.

AND if the ColdFusion session times out, I also want the Angular 4 app to notice this and redirect the user to the /login route.

Basically I need to secure both the client-side (using an Auth-Guard-style service in Angular 4, which I know how to do) and the server-side (using ColdFusion 11 Session Management, which I do not know how to do), and I need them to communicate constantly about the authorization status of both, without having to ask every single time whether or not the session is still valid. (Can the Angular 4 app somehow read the ColdFusion session cookies?)

How do I get these two things to cooperate with each other like that? Or is my ignorance of ColdFusion session-management blinding me to a far better solution that I haven't thought of yet?

Any suggestions are appreciated. Thanks!


Solution

  • On the server, cfc's are not exempt from automatic session creation and cookie management

    For a request to have access to session variables, these conditions must be met:

    • The client must make a request that gets routed to ColdFusion (i.e. it hits a cfc or cfm, not some static html or js).
    • There must be an Application.cfc in the same directory or some ancestor directory of the one where the requested cfm/cfc is.
    • The Application.cfc must enable session variables with this.sessionmanagement = true;

    When those conditions are met, ColdFusion will associate the request with a session. There are 3 ways this association can me made:

    • The client already has valid session cookies and sends them in the request. Your CFML code can read session variables that were created in previous requests, and set new values for future requests to read.
    • The client is new, and has no cookies. ColdFusion creates a new set of cookies and a new session scope. Your CFML code can set session variables for future requests to read. The new cookies are automatically sent to the client along with your response.
    • The client sends cookies, but they correspond to an expired session. This is handled just like the previous case. New cookies are sent and an empty session scope exists for your CFML to fill.

    On the client, ajax requests are not exempt from cookies either

    The underlying XMLHttpRequest gets and sets cookies from the same cookie store as all other requests. If the requested URL matches the domain, path, secure flag of a cookie, XMLHttpRequest will send the cookie. And if it gets valid cookies in response, it will add them.

    Mostly you just use session variables without thinking about cookies or how they got there

    So for your use case, if your login page is internally routed to login.cfm, and there's an Application.cfc nearby, the session scope is ready for you to use as soon as login.cfm starts. You can do

    if(IsDefined("form.username") && IsDefined("form.password")) {
      if(...check password [aka the hard part]...) {
        session.user = form.username;
        location(url="/home");
      } else {
        location(url="/login");
      }
    } else {
      ...print the login form...
    }
    

    And your logout code can StructDelete(session, "user")

    Everywhere else, in all your cfc's and cfm's, the question of whether the request came from a logged-in user is simple: if the client has previously logged in, and the session hasn't expired, then session.user exists. Otherwise it doesn't (you will have a session - there is always a session because ColdFusion creates one before running your CFML code - but there will be no user variable in it until you put one there).

    You can set other user-related variables in the login request too (and unset them at logout), like real name, preferences, anything you want to load from a database that will be frequently used and infrequently updated, you can keep in the session scope. Also there's cflogin which is supposed to help with managing user logins, but it seems pretty unnecessary. (See Why don't people use <CFLOGIN>?)

    Your desire to avoid "having to ask every single time" is not really fulfilled, but the "asking" is minimal. The client sends the cookies in every ajax request, which is effectively "asking" for the session to be continued. And it must check every ajax response for the "session timeout" error. And on the server, every request-processing function must begin with a check for existence of a session variable.

    But you can use an ajax wrapper on the client to ease the pain.

    On the server, you can use an onRequestStart to provide a common "precheck" to all requests so you don't even need to have if(...no user...) { return "OH NO"; } at the top of every function.