Wednesday, 26 November 2014

Logging in without user intervention

For this, I set up an authentication plugin and behind the scenes it's using a web service to check if the client is valid or not.

To automate the login process, you need to make use of the sentry function that gets called every time you access a page in the application. This is what the item help says:

Enter the name of the PL/SQL function the plug-in can use to perform the session sentry verification. It can reference a function of the anonymous PL/SQL code block, a package function or a stand alone function in the database. For example:

check_ldap_session_sentry

When referencing a database PL/SQL package or stand alone function, you can use the #OWNER# substitution string to reference the parsing schema of the current application. For example:

#OWNER#.check_ldap_session_sentry


There is however one caveat with this. It doesn't run on whatever you have defined as the login page (User interface attributes --> Desktop --> Login URL) - which is reasonable enough.

So I left that attribute alone, and created 2 pages that will aid in the process:
  1. Logout page
  2. Invalid session page
The idea being, when the user logs out, they will be taken to the logout page. I got this idea from the Apex Builder actually - with a button to return to the system. I think the best page to return to is the page specified in HOME_LINK. Using the substitution &HOME_LINK. doesn't seem to work, since by default it includes a substitution string for &APP_ID. which doesn't seem to be evaluated. I think a good workaround is to create a new substitution string: &HOME_PAGE. and use that in a redirect to page button action (then update your home link to reference that substitution string).

When the user attempt to access a page and authentication/sentry fails, they will be taken to the invalid login page.

We have 2 associated attributes in the authentication plugin:

  1. Post logout function
  2. Invalid session function
These are really just to determine which page to go to next - which line up with the 2 pages I created earlier on. 

When we logout, we set the page to go to, but because the session is also invalid, it will get fired as well. Since we don't want to get redirected to the invalid session page when we log out, we just need one additional check in that function to make sure the current page isn't the logout page. Here is how I implemented that:

function invalid_session (
    p_authentication in apex_plugin.t_authentication,
    p_plugin         in apex_plugin.t_plugin )
    return apex_plugin.t_authentication_inval_result
AS
    l_invalid_Result apex_plugin.t_authentication_inval_result;
BEGIN
    
    if apex_application.g_flow_step_id != LOGOUT_PAGE
    then
        l_invalid_result.redirect_url := 
            'f?p=' 
            || apex_application.g_Flow_id
            || ':'
            || INVALID_SESSION_PAGE ;        
    end if;

    return l_invalid_Result;
END invalid_session;

Nothing fancy about the logout function:

function logout (
    p_authentication in apex_plugin.t_authentication,
    p_plugin         in apex_plugin.t_plugin )
    return apex_plugin.t_authentication_logout_result
AS
    l_logout_result apex_plugin.t_authentication_logout_result;
BEGIN

    l_logout_result.redirect_url := 
        'f?p=' 
            || apex_application.g_Flow_id
            || ':'
            || LOGOUT_PAGE;         

    return l_logout_Result;

END logout;

Then for the actual sentry function. I return true if it's one of the pages created above (Logout or Invalid Session page). Then, where necessary, I call the authentication function to get the appropriate result. Since the parameter p_authentication doesn't contain the username attribute on invalid sessions, I declare a local variable of type apex_plugin.t_authentication then copy all the values from p_parameter except p_username - which I'm retrieving in my own function.

l_auth_attrs.id                     := p_authentication.id;
l_auth_attrs.name                   := p_authentication.name;
l_auth_attrs.invalid_session_url    := p_authentication.invalid_session_url;
l_auth_attrs.logout_url             := p_authentication.logout_url;
l_auth_attrs.plsql_code             := p_authentication.plsql_code;
l_auth_attrs.session_id             := p_authentication.session_id;
l_auth_attrs.username               := upper(get_user_name());
l_auth_attrs.attribute_01           := p_authentication.attribute_01;
l_auth_attrs.attribute_02           := p_authentication.attribute_02;
l_auth_attrs.attribute_03           := p_authentication.attribute_03;
l_auth_attrs.attribute_04           := p_authentication.attribute_04;
l_auth_attrs.attribute_05           := p_authentication.attribute_05;
l_auth_attrs.attribute_06           := p_authentication.attribute_06;
l_auth_attrs.attribute_07           := p_authentication.attribute_07;
l_auth_attrs.attribute_08           := p_authentication.attribute_08;
l_auth_attrs.attribute_09           := p_authentication.attribute_09;
l_auth_attrs.attribute_10           := p_authentication.attribute_10;
l_auth_attrs.attribute_11           := p_authentication.attribute_11;
l_auth_attrs.attribute_12           := p_authentication.attribute_12;
l_auth_attrs.attribute_13           := p_authentication.attribute_13;
l_auth_attrs.attribute_14           := p_authentication.attribute_14;
l_auth_attrs.attribute_15           := p_authentication.attribute_15;

l_auth_result := 
    authenticate_user(
        p_authentication => l_auth_attrs
      , p_plugin => p_plugin
      , p_password => NULL);
      
if l_auth_result.is_authenticated
then

    apex_custom_auth.define_user_session(
        p_user => l_auth_attrs.username
      , p_session_id => apex_custom_auth.get_next_session_id  
    );

    l_sentry_result.is_valid := l_auth_result.is_authenticated;
else

    l_sentry_result.is_valid := false;
end if;

Well that about covers it! Good luck with your authentication adventures!

2 comments:

  1. Hi,

    nice post about authentication plugins, which are quite underutilized. I blame myself for not explaining more about the internals, but it's much more fun to develop stuff than to write about it ;-). I do have a question about your approach, though:

    The reason for introducing the post logout hook was mainly to give developers an entry point for cleaning up resources that are external to APEX and related to a session. For example, we use this in the internal schemes to log out of single sign-on after the user logged out of the APEX application. For your use case, did you consider simply setting a post-logout URL in the authentication scheme?

    ReplyDelete
    Replies
    1. Hi Chris,

      That was just an oversight on my part. I was on the apex_plugin API documentation and noticed the t_authentication_logout_result type has one attribute, redirect_url, and assumed that would be the best way to go about it.

      I've taken your advice and fixed that up.

      Cheers,
      Trent

      Delete