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
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 login
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.
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:
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:
To 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
);
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.
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.
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
We 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.
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
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
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.
ReplyDeleteHi David,
ReplyDeleteGood 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
Hey Christian,
ReplyDeleteI remember this subject was the first we had a discussion on, together with Sergei in Brussels. Great blog, I'll investigate soon.
Regards,
Richard
Hi Christian
ReplyDeleteI 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
Hi Christian
ReplyDeleteI 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
Hi Christian,
ReplyDeleteThanks 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
Hi Joost,
DeleteI 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
Hi Christian,
ReplyDeleteThanks for your quick response.
Indeed this solves the problem.
Grtz,
joost
Hi Christian,
ReplyDeleteI 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
Hi Faraz,
DeleteLet's discuss this offline. Just send me an email (christian@rokitta.nl) describing the problem you are facing.
Regards,
Christian
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 :)
ReplyDeleteGot 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.
Hi Christian,
ReplyDeleteI 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.
Hi Christian,
DeleteWell, 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
Hi Christian,
DeleteThank 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
For those of you that are using apex accounts i used the following workaround for the Remember Me option.
ReplyDeleteI 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.