Remember Me - APEX Autologin

I promised to publish about this subject to one of the attendees during my presentation at KScope 12 in San Antonio. I used this functionality in my demo application (FifApex) and it seems there is interest in how to do this.

Most of the public websites like Facebook, LinkedIn, Twitter, Amazon or OTN do have it: an option to stay logged in, even if you closed the browser, so you do not have to authenticate each time you visit the site again. I’m pretty used to it and would be surprised if a website didn’t offer this convenient feature. OK, it’s not entirely secure, but, as I said, very convenient.
I’m working on a “consumer” site/application, I’m building it with APEX and I want to offer this “remember me” option too. I knew an APEX based website that does offer this feature (www.plsqlchallenge.com) and I had a chat with the developer that implemented it’s login mechanism, Paul Broughton from Apex Evangelists. So, here my thanks to him for the original inspiration.
 
Oracle Application Express has neither a build in functionality nor is it providing a “standard” Authentication Schema that does provide this mechanism. But, with just a little effort you can implement this Autologin in your application. My example uses a Custom Authentication schema, meaning I have a user table and a package, providing all the necessary functionality. At the bottom of this post, you will find links, where you can download all files you need to install the demo application in your own environment.

Let’s start

remember_me_twitter
First of all, let me define the mechanism: the user gets an option, usually a checkbox, in login screen to stay signed in. Next time the use visits the site, he doesn’t have to provide his credentials and is automagically signed in.
The common technique to achieve this, is using a cookie that holds a token to identify the returning user. So we have to set a cookie when the user signs into the application and check for the cookie and validate the token when the user comes back.
Setting and reading a cookie is easy, using the OWA_COOKIE package, but how to integrate this into a APEX authentication schema?
The following example is build with a standard “empty” application with Custom Authentication. Prior I created a table to hold the user data and a package, containing the authentication logic. Note: this is a simplified example, so using this in your production application is at your own risk! All code is included in the demo application download.

CREATE TABLE my_custom_users(
   username   VARCHAR2(25 BYTE)
, password   VARCHAR2(250 BYTE)
, token      VARCHAR2(25 BYTE)
);


INSERT INTO my_custom_users(username, password, token)
VALUES ('DEMO', my_cust_auth.encodeit('DEMO', 'demo'), NULL);

COMMIT;

Step 1: setting the cookie during loginremember_me_01

First I’m going to modify the generated Login Page (101), adding the “Remember me” checkbox.
Then I modify the “Set Username Cookie” process that already should exists and stores the APEX username in a cookie (I do not make any changes to this functionality): The code I add, checks, if the “Remember me” has been ticked by the user, the user actually is a valid user from my table and then sets a cookie, called “REMEMBER_ME”. The value of this cookie is generated by a function, producing a random string and storing this as a token with the user data or fetches the token from the user data, if already existing. In my example, I choose to set the expiration date of this cookie one to about a year.
remember_me_02
Running the application, logging in with the checkbox ticked, you can exam the cookies of your APEX application by using a tool like the Developer Console of Chrome. Next to some cookies, set by APEX itself, you should find the “REMEMBER_ME” entry holding the token string and expire date of today + 365 days:
remember_me_03

Step 2: using the cookie on return

Now, when do we need to read the “REMEMBER_ME” cookie again? Every time the visitor returns to your site/application, has not signed in yet (obviously, as we want to do this automagically) and the cookie is set and holds a token that is known in the user table (assuming that the user is the same again!). I want to perform the check, regardless of the page visited is a public page or a page that requires authentication. The event that should be triggered, if the conditions are met (cookie set and valid, user is public), is the a automatic login, similar to the original login. To perform the check, I will use “PAGE 0”, but lets first create the autologin functionality to be called:
remember_me_04To realize the autologin, I create a new page (103 in example application). This page only contains a “On Load - Before Header” process and one page item. The process only fires, when the request name is “AUTOLOGIN” and calls the build in standard APEX login procedure provided for custom authentication. It uses the page item to “P103_TOPAGE” as target page after successful login, which I will set on the triggering process/branch on page 0, to return to the page the user actually requested in the URL. The username is derived from the cookie (the token belonging to one unique user) using the call OWA_COOKIE.get ('REMEMBER_ME'); in a stored procedure.
 
wwv_flow_custom_auth_std.login(
    P_UNAME       => my_cust_auth.get_user_from_cookie,
    P_PASSWORD    => null,
    P_SESSION_ID  => v('APP_SESSION'),
    P_FLOW_PAGE   => :APP_ID||':'||:P103_TOPAGE
    );
 
As you see, the password parameter is NULL. So I have to add logic to the Authentication Function (my_cust_auth.validate_user) of my custom Authentication Schema, that handles two cases: either a valid combination of username/password is given, or the “REMEMBER_ME” token is set and a corresponding user is found. Again: this is very basic and may not be sufficient for your production application.
 
 

Step 3: automagically do it

I already mentioned that I will use Page 0 to call the auto login. Doing so, the user should be singed into the application, regardless which page he opens first. I simply create a “After Header” Region of type “Dynamic PL/SQL”. The redirect will be done using the APEX build in procedure sys.owa_util.redirect_url, passing the relative URL of page 103 with request parameter “AUTOLOGIN”. This region is conditional, checking for: user is public, cookie is set and belongs to “a” user.
remember_me_05a
remember_me_05b
remember_me_05c
I added an extra condition checking for the current page ID to be less or equal to 101, to prevent page 103 from recursively calling itself. Page 102 will be this demo application’s special logout page (see next section). All “normal” pages in this application are assumed to have ID’s in the rage of 1 to 100. You definitely should modify this condition to meet your actual applications page ID ranges.remember_me_08
Note to myself: I first tried to use a “Before-Header” Branch in Page 0 to implement this redirect. But: Branches in Page 0 do not ever get executed. It’s not the first time I hit this pitfall. After all: the APEX 4.1 Application Builder Treeview of Page 0 suggests Branches are possible.

Step 4: forget me

remember_me_07We need to offer the user a way to logout and remove the cookie. I create a new page, 102, containing one “On Load - Before Header” process and a branch.
The process removes (expires and replaces value with empty string) the cookie and performs the actual logout using WW_FLOW_CUSTOM_AUTH_STD.LOGOUT. The branch will take the user to the (public) Home page (1).
I then specify to use page 102 as the Logout URL of my Authentication Schema.
remember_me_06a
remember_me_06b
The request name “LOGOUT” is provided by APEX itself, so you might check for it in a condition, rather then defining the process as unconditioned, just to prevent accidental logout.
So, that’s it. Just a few simple modifications to your custom authentication. What do you think? Does anybody have a more elegant solution (I’m sure there are)? I would appreciate it, if you would post your ideas as comments to this article. If you haven’t got a solution yet, but want to use mine: go ahead! Down here the links of the demo application on apex.oracle.com and the full download of it for you as a template.

 

Demo and Download

menu-run-128
menu-expimp-128
Try the demo at apex.oracle.com!
(login with demo/demo)
Download the DemoApp!
(just import and install incl. supporting object scripts;
min. version: 4.1;
default login: demo/demo)

 

Possible Enhancements:

The whole mechanism is a rather simple and naïve approach. I just wanted to explain the basic principle of it. There are lots of enhancements and improvements one could think of, and actually, while writing this post I thought of some myself:
  • using pre/post function call of Authentication Schema instead of page processes
  • integrate autologin logic from page 103 to page 101
  • cookie name application variable or dynamically generated
  • investigating the possibility of an autologin authentication plugin

 

themes4apex

Comments

  1. Thanks a lot for this. It's something I've wanted for a long time, but have never taken the time to actually figure out and implement! I've added it to my site, but without pages 102 and 103 (some careful conditions on page 101 allow everything to co-exist happily). Also changed the page 0 condition to only fire on pages which require authentication.

    ReplyDelete
  2. Hi David,

    Good idea. I was thinking of integrating page 102 and 103 too.
    The reason I did not constrain the autologin to page that require authentication is, that I wanted returning users to be logged in immediately, in case I want want to show personal information, like notifications, or menu items, that otherwise would be hidden. I guess, it depends on the purpose of your web site/application. As I said: it's a template or even less, just a how-to.

    Regards,
    Christian

    ReplyDelete
  3. Hey Christian,

    I remember this subject was the first we had a discussion on, together with Sergei in Brussels. Great blog, I'll investigate soon.

    Regards,
    Richard

    ReplyDelete
  4. Hi Christian

    I created a modified version of what you have, it doesn't have page 0 to redirect to autologin page. this was causing me issues..

    I have 101, as my main login page, (so Apex automatically redirects to 101 (login) when not authenticated. but all it does is check cookie has token etc..
    and if no valid cookie/token it redirects to another page with login/password, where it sets token cookie

    Works well

    Dean

    ReplyDelete
  5. Hi Christian

    I made a modified version of your login.. after having difficulties with page 0 redirect..

    My version doesn't have page 0 redirect..
    I made my std login page the autologin page.. so apex automatically goes to this page when not authenticated..

    And only runs 1 PL/SQL
    DECLARE
    c OWA_COOKIE.cookie;
    l_token eng_users.token%TYPE;
    l_username eng_users.username%TYPE;
    BEGIN
    c := OWA_COOKIE.get ('REMEMBER_ME');
    l_token := c.vals (1);

    Select username
    into l_username
    from eng_users
    where token=l_token;

    IF length(l_username)>0
    THEN
    APEX_CUSTOM_AUTH.post_login (l_username, V('APP_SESSION'),:APP_ID||':1', TRUE);
    ELSE
    apex_util.redirect_url ('f?p=&APP_ID.:109:&SESSION.');
    END IF;
    EXCEPTION
    WHEN OTHERS
    THEN
    apex_util.redirect_url ('f?p=&APP_ID.:109:&SESSION.');
    END;

    So it redirects to a page with Username/Password if no valid cookie/token exists

    ReplyDelete
  6. Hi Christian,

    Thanks for you the blog. It works perfect on a desktop interface.
    But on a jquery mobile interface it doesn't work for me.
    Do you have an idea how to solve this.

    Thanks in advance.

    Grtz,

    Joost

    ReplyDelete
    Replies
    1. Hi Joost,

      I have successfully implemented the approach in an APEX 4.1 application using jQueryMobile: http://m.fifapex.net.

      One thing to keep in mind: the standard jQM way of getting a new page is fetching it using an AJAX call. The autologin functionality requires a HTTP call when logging in with the “remember-me” checkbox set, to set the token-cookie. In my application I disabled the AJAX behavior for the login-button by adding a jQM data attribute to it: data-ajax="false".
      Hope this helps.

      Cheers,
      Christian

      Delete
  7. Hi Christian,

    Thanks for your quick response.
    Indeed this solves the problem.

    Grtz,

    joost

    ReplyDelete
  8. Hi Christian,

    I m facing problem after importing the script into apex. When i try to connect to the application using the given credentials, i get an error "Invalid Login Credentials, Wait for 40 seconds".

    Secondly, why are we using apex_authentication package for validation and how my_custome_users table is populated and how it is validated at the relogin time of user.

    Regards
    Faraz Saleem

    ReplyDelete
    Replies
    1. Hi Faraz,

      Let's discuss this offline. Just send me an email (christian@rokitta.nl) describing the problem you are facing.

      Regards,
      Christian

      Delete
  9. Thanks very much for this. I read this back in 2012 when you first posted it and put it in my list of bookmarks to get to later - well I finally got around to it :)

    Got it working quite well. I ended up putting the autologin code just on my home pages (desktop and mobile interfaces) which and put the AUTOLOGIN and LOGOUT request handlers on the login pages, instead of creating new pages for these functions.

    Apex 4.2 doesn't expose the Logout URL parameter in the authentication scheme so I just changed the Logout buttons to navigate to the login page with the LOGOUT request.

    ReplyDelete
  10. Hi Christian,

    I tried you demo on apex.oracle.com. I signed in checking the box 'remember me'. Then I signed out. When I signed in again, I still get the authentication page with the 'remeber me' box not checked.
    What is wrong ?
    Thank you.

    ReplyDelete
    Replies
    1. Hi Christian,

      Well, you "signed out". This explicitly removes the "Remember Me" cookie. This is nessesary, in case you want to sign in as a different user or just "really" want to sign out.

      The Remember-Me works if you just close the browser, without singing off.

      I think this would be the behavior one would expect.

      Kind regards,
      Christian

      Delete
    2. Hi Christian,

      Thank you very much for your fast answer. I understand what you said now ! WW_FLOW_CUSTOM_AUTH_STD.LOGOUT is deprecated in Apex 5.0, instead I used successfully APEX_AUTHENTICATION.LOGOUT. I am faced with another problem. Page 0 does not seems to be executed. After signing in, checking 'remember me' , I close the page. I run the application from the Designer, I land on page 1, the Home page (which is Public). I can see the cookie 'REMEMBER_ME' is still there. But I am not automatically signed in. I need to sign in again if I want to access to pages requiring authentification. It seems that page 0 (which is page number 0 in my application) does not seems to be executed.

      Best regards.
      Christian

      Delete
  11. For those of you that are using apex accounts i used the following workaround for the Remember Me option.

    I created and after authentication process that used apex_util.set_preference to remember the username and ip_address when Remember me was checked. I had to modify the Clear cache process on the login screen to clear only the username and password an leave the Remember me item untouched. The variable user for set_preference was sys_context('USERENV','ip_address') which retrieved the pc's IP.

    apex_util.set_preference('AUTOLOGIN',:APP_USER,sys_context('USERENV','ip_address'))

    This way i could use APEX_UTIL.GET_PREFERENCE('AUTOLOGIN',sys_context('USERENV','ip_address')) to read the username before header in the login page.

    If APEX_UTIL.GET_PREFERENCE('AUTOLOGIN',sys_context('USERENV','ip_address')) was not null the i'd log in the user with some dummy credentials (user Demo, password Demo) that i had previously created and had no authorization.

    So now i had my user logged in with some dummy credentials.

    The only thing left was to create another before header application process to set the current application user to the value already stored in the AUTOLOGIN preference:

    wwv_flow.g_user:=APEX_UTIL.GET_PREFERENCE('AUTOLOGIN',sys_context('USERENV','ip_address'));

    I'm not sure how safe this method is but for my use-case it worked like a charm. Maybe someone else would find it useful.

    ReplyDelete

Post a Comment

Popular posts from this blog

Tabular Forms on Complex Views - using INSTEAD OF Triggers

Book Review: Oracle APEX 4.0 Cookbook