?
Solved

PHP sessions and back button

Posted on 2012-08-28
45
Medium Priority
?
400 Views
Last Modified: 2012-09-25
Hi experts,

A familiar subject, I know, but I'm struggling to find a solution to my own variation of

1. logging out;

2. hitting the back button once or twice;

3. being asked to refresh the page and doing so,

4. and lo and behold, logging in without explicitly doing so.

I have read a few suggested solutions, and am using the header one in my controller, attached.

But still I can get back in after logging out - where am I going wrong?

Thanks for reading,

  Col
index.php
0
Comment
Question by:colinspurs
  • 21
  • 10
  • 8
  • +1
45 Comments
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38340872
The first part of this answer is understanding the reason why this happens.

If all it takes to get into a secret nightclub is to know the password, then... that's all it takes, right? If I leave the nightclub, then go back to the doorman and repeat my password, then he will let me in again, because ALL I need is the password.

Your browser holds form data in its immediate history. Those header() lines about caching have no effect on that, so when you hit back a few times and see that message to refresh, the browser is telling you, "Hey, I still have all that form data you originally sent in order to see the "thank you for logging in" page - do you want me to try to recreate that experience by re-sending what I sent before?" and when you say yes, it sends the credentials just like the first time.

Second part...
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38340899
Ah. OK I understand the 1st part but thought the header code would sort that...
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38340903
I like the analogy btw.
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38340916
...is to introduce some new criteria into the authentication so that sending the EXACT same data will not work to log you in. Put simply, this means your "doorman" needs to have some kind of dynamic authentication measure that he checks along with the password.

Here are a couple of examples:

1. Just like when you go to the DMV or the doctor, you have an appointment or a ticket that gets generated and assigned to you. You use that ticket to get to the next step in the process, and when you use the ticket, it gets consumed and destroyed so it only works once. In your case, you might have a PHP script that generates a random string like "adfadf319ud__!!!!adk_=!#*$&gnwouhw143134-9dajf31" and puts it into a database table that holds "valid tickets". Then the string just sits inside of a hidden input on your <form>. When you log in with your credentials, that random ticket is looked up in the "valid tickets" table, and if found, it deletes the ticket and says, "OK, -now- let's look at your username and password..." Then later, when you go back and the browser tries to resend the same form data with the same ticket, the ticket isn't going to be found, so it won't let even bother checking the username/password - it'll just reject the login.

2. A simpler version of the above, you can store the current timestamp in a  hidden input. So let's say that a visitor came to the page at 11:00:00 AM and logged in at 11:00:32 AM. The server can check the timestamp that was sent in that hidden input and compare it against the current time. If the difference is greater than 60 seconds, for example, then the server rejects the login. That gives new visitors up to a full minute to log in if they want. Later, when they try to go back and refresh/resend the form data, it's going to resend the old timestamp, which is likely going to be older than 60 seconds, so your system can reject it before checking the username/password.
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38340923
Put a hidden variable on your page with a one time password linked to a use count or expires time.

If you back page and re-submit either the OTP is invalid (cos you used it already) or the time has expired.

That's the way I do it anyway.
<form ....
  <input type="hidden" name="otp" value="ab23ge9acfa1-091a-198a-def3-a87cg6f1" />
  <!-- rest of form data here -->
</form>

<?php
   // Not actual code - for illsutration
  $otp = $_POST['otp'];
  if (check_otp_valid($otp)) {
     do_logon();
  }
...
function check_top_valid($otp){
   $query = "SELECT * FROM otp_table WHERE otp='$otp' AND used=0";
// or
   $query = "SELECT * FROM otp_table WHERE otp='$otp' AND expires > Now()";
// execute query
   return  (mysql_affected_rows() > 1) ; // Return true if a row was found false otherwise
}

Open in new window

0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38340963
My understanding is that the header() lines will affect the storage of the page content, but not the temporary form data in the immediate history. I haven't read anything recently on cache headers, so there's a chance my understanding may be a bit old. That said, the header() lines are instructions to the browser, but it is up to the browser to determine how to follow those instructions, so there's a chance of different browsers doing different things with those caching headers.

The code approach takes the control out of the browser's hands.
0
 
LVL 111

Assisted Solution

by:Ray Paseur
Ray Paseur earned 320 total points
ID: 38340988
I think it is easier than it may seem.  

1. Eliminate the session variables that have the client authentication data.
2. Eliminate the cookies associated with the session and authentication.

After those two steps, the stateful connection between server and client is broken.  Even if the client somehow manages to send a valid cookie, the session data is not there on the server any more.

Over and out, best regards to all, ~Ray
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38340996
julian's presented a good example of what #1 might look like. If you go down that route, I would add just one more thing to make it a bit more efficient (optional). Instead of generating a ticket for every login form presented to your users, use Javascript and AJAX to request a ticket from the server right before login:

1. User types in Username and Password and clicks on Login.
2. Login button disabled and label changes to "Please wait..."
3. AJAX goes out to a script on your server that generates a new ticket, puts it into the database table, and returns the ticket.
4. Javascript gets the ticket and puts it into a hidden input.
5. Javascript submits the form.

Overall, the process should be very quick, and the "Please wait" messages always buy you a couple of seconds of extra patience from your visitors (as long as they know their login request is being processed). This also means that the ticket will be generated and then be consumed /deleted in a matter of seconds, so your database table of valid tickets will remain relatively small (even empty, most of the time). You'll also be able to easily clear out old tickets that were created but not used (e.g. a browser crashed) if they are more than a minute old.
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38341077
@gr8gonzo - actually I provided a sample for both. The second query deals with the timeout - the difference is that I did not store the actual time on the page because this can be spoofed (not really in the scope of this question because we are talking about back buttons and not spoofing) - but still seems better to me to put some value that has no meaning outside of the program that created it.

@Ray - have not given this too much thought but isn't the problem that even if you abandon the session and invalidate the cookie that you are resubmitting a logon form which basically then reinitialises the session and the cookie as it is now "logging on" again?

Maybe I missed something in which case your suggestion is simpler but I was going on gr8gonzon's analogy about the doorman - if the browser caches the form submission - without putting something else on the form the server has no idea that the login is coming from a cached page.

As far as AJAX is concerned - relies on JScript being activated. So now I am on a public machine - disable javascript and logoff won't work - creates a bit of an issue.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38341272
Thanks as ever guys.   I'll let you know how it goes.

Col
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38341479
@julian - Sorry, I missed the expiration thing.

I'm personally in favor of relying on AJAX and Javascript, even if it can be disabled (or even if it is forcibly disabled on public machines). There is always a cost to every benefit, and I think the long-run benefit of using AJAX/Javascript (which also implies being able to use it on the rest of the site) far outweighs the cost of potentially excluding a few users that are on public machines that happen to also have JS disabled. (The vast majority of users on a private machine do not disable Javascript on a regular basis, which leaves disabled-JS-browsers to be mostly older mobile devices, which aren't usually accessing those applications anyway.)

Javascript is becoming invaluable to modern security mechanisms. Google started deprecating their Javascript-free approaches back in 2007. I don't think you can even log into GMail today with Javascript disabled. Whatever the case, trying to cater your site to a non-Javascript audience is becoming more and more impractical. Many public libraries and other public machines recognize this and are opening up that option.
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38341592
@gr8gonzo - just reread your post - misunderstood the first time - thought you were implying that you use Javascript / AJAX to do the logoff - which would effectively leave you with the same problem as the logon details are still cached.

Will try reading all the words next time instead of every 4th or 5th.

I agree that AJAX in the manner you describe will work well - but it does add a level of complexity onto the solution. Putting an OTP into the page when it is generated amounts to the same thing without having to add any client side code.
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38341950
Yes, it's an additional level of complexity. I'd recommend getting the main token/ticket/OTP mechanism working first, and then adding the AJAX portion later to improve the efficiency of the system.
0
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 38342058
Hi, julianH.  To the comment at ID: 38341077...

The login should be a POST-method request since it changes the state of the server.  The form fields should be of type="text" and type="password" and you might try using autocomplete=off if you want a small added measure of security.  Traversing the browser history should never cause a POST-method request to be repeated without a warning to the client.

As to the described behavior, I am reading this from the original post...

1 logging out;
2 hitting the back button once or twice;
3 being asked to refresh the page and doing so,
4 and lo and behold, logging in without explicitly doing so.

The part in #3 has me a bit stuck.  Perhaps Colin sees something like "You have already submitted the form.  You must refresh this page to re-send the data."  And if at that point the client refreshes the page to re-send the login credentials, I am not sure what I can do to help that client understand that re-sending the login credentials will be the same as logging in again!

As a practical matter the design from the article does not let this happen.  I have tested for that condition using the code posted in the article.  It doesn't occur.  After the logout, when you hit the back button, if your traversal of the browser history causes you to go back to a protected page, the access control mechanism intervenes and requires a login.  Once the login is completed, the protected page is shown (again).  So my conclusion is that there may be a logic flaw in the login/logout process and that is why Colin sees the unusual behavior.  Maybe we could debug this if we could see the PHP script loaded by this statement:
require_once(LIB_PATH.DS.'access.inc.php');

The situation may be different if the client does not log out, goes back through the history, then goes forward.  In that case, the cookies that are associated with the PHP session and possibly the "remember me" cookie would still be presented with each request, and the client would still be logged in.  But that does not fit with the described behavior.
0
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 38342070
Sidebar note to gr8gonzo.  I completely agree about JavaScript.  Looks like it's here to stay, and jQuery makes it relatively easy to improve the client experience.  However I still believe that it's important to validate the external inputs on the server side of things, after the data has been transmitted from the client, and the client can no longer change the data.  Only in that way am I comfortable that my server and data model are safe.

Cheers, ~Ray
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38342211
Ray,

You are correct in your assumption re (3)..."You have already submitted the form.  You must refresh this page to re-send the data.".

Having written my login/logout code I was giving it a bit of a workout.  The scenario being...Hero logs out and goes to lunch leaving PC on, Villain comes along and hits back button and logs in as described.  I wondered what the standard method of counteracting this is.  If indeed there is one - if it is acceptable behaviour I could live with that too.

Just now, log in and log out php/html are included as part of my controller code, but it would be no trouble to shift them to their own pages if that helps.

Unfortunately it would be difficult to provide an accessible example as it's all in XAMPP on my PC at the moment.

I attach the access.inc.php code.  It is based on the Sitepoint book "Build your own Database Driven Web Site..."

Col
access.inc.php
0
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 38342282
That's not acceptable behavior -- if Hero logged out, it must not occur.  On the other hand if Hero leaves the room and leaves his session logged in, Hero has nobody to blame but himself!

I looked at the code, and while it could use some else{} clauses, I do not immediately see anything that would cause troubles -- except I would never keep the password (even the encoded password) in the $_SESSION array.
0
 
LVL 111

Expert Comment

by:Ray Paseur
ID: 38342296
Col: Here's another thing to take into account.  This is rarely a problem in "real life" but it sometimes drives developers nuts.  

All instances of the browser share the same cookie jar.  What that means is that each and every window or tab of your browser will return the same cookies to the same site.  If you open a FF window and log in, then you open another FF window and access the site, you're already logged in.  If you log out from one window, your next request from the other window will come from a logged-out browser.  Watch out for this in your testing.  It can create a lot of head-scratching moments if you don't take it into account.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38342327
<<I would never keep the password (even the encoded password) in the $_SESSION array.>>

I can see that.  I'll revisit the code to see if/where it's actually required and try to circumvent.

****************

<<All instances of the browser share the same cookie jar>>  

That is to be the next stage of my testing.  It would drive me nuts.  

Col
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38342413
re OTP

I did a search for php & otp and found all kinds of complex classes requiring mobile devices to generate the otp etc.

This is what I have made of Julian's code.  Would "AND used = 0" be necessary if I based it on time?

<?php
      // Generate otp based on encrypted string and time.
      $otp = md5( 'f%Y*Dnt8_^2cjDM' . now() );  

      // Check otp table to see if it had been used already.
      SELECT * FROM otp_table WHERE otp = $otp;
       
     If (otp found)
     {
          deny login
     }
     else
     {
           INSERT INTO otp (values $otp);
           Check ID and password as usual.
           If OK allow login and proceed...
     }
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38342829
@colin - actually it is very simple. You can use a number of different methods - I have a function that generates a random string of N characters (which I use to generate salt for passwords) but most of the time I simple do a

SELECT UUID();

Against the db to generate a GUID - extra DB call but it works well. Did some profiling on it and the hit is negligable.

Re the question on AND - yes you can do the AND but it is not really necessary. If it is a OTP as soon as you set the used to 1 the query won't return that record as an option but definitely use the AND if you want to put a timelimit on the OTP.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38342929
No, that wouldn't work, it would be unique every time.   I need a random string generator as suggested!
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38342942
<< that wouldn't work >> meaning my own suggestion.  Posts crossed!
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38345054
I have really tried but I can't get my head around this...the new access code is attached.

I set a cookie to the uuid when I log in and destroy it on logout, but this doesn't solve the back button issue.  Of course if I don't destroy the cookie on logout, it is always detected in the db on login so I can't log in.

Where am I going wrong?

Colinaccess.inc.php
0
 
LVL 35

Accepted Solution

by:
gr8gonzo earned 880 total points
ID: 38345171
Several recommendations:

1. Move session_start() out of your functions and just place it once at the top of some header file that every page includes.

2. The logic should be:
- OTP gets generated when the cookie is empty and only on a regular page view when there is no login attempt.
- If there is a login attempt, then you should only CHECK the OTP to see if it's valid. If an OTP doesn't exist during a login attempt, then redirect to the home page or something. Otherwise, when you hit the back button and refresh, the system would see a lack of an OTP, create one, and then immediately consume it. :)

3. Don't use SQL to generate the UUID. PHP has a function for this already:
$otp = uniqid(session_id(),true);

4. In your newOTP() function, your first if() section should perform the database check to see if the OTP in the cookie exists. If it DOESN'T exist, that means the user has a cookie value but the database doesn't. This really shouldn't happen unless someone is messing with their cookie values. If that's the case, then you really should not insert that value into the database. Instead, go regenerate a new UUID and update the cookie.

If the query returns a row, though, then that means that the OTP in the cookie has been checked against the database. At that time, you should delete the OTP from the database and clear the cookie. If login fails, it should redirect to a page so a new OTP gets generated or else generate a new OTP right then and there.

5. You should have a column in your one_time_passwords table to hold an expiration timestamp of the OTP. This way, you can regularly run a job that deletes all OTPs that have expired and keep your database clean and more secure:
$db->query("DELETE FROM one_time_passwords WHERE expires <  " . time());
Make sure your cookies have the same expiration so that they get new OTPs automatically if needed.

6. You don't need to destroy the session on invalid login attempts. In fact, preserving the session can give you a slight edge in that you can have a counter of bad login attempts in the future if you want it.
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38345188
I also concur with an earlier statement that you shouldn't store the password, even a hashed version, in the session. Session files are stored on a server's filesystem, often in the /tmp folder, and if a malicious user gains access to read them, he would gain a good amount of information about your system, including the valid hashes (which could come in handy if you are ever exposed to a SQL injection attack).

Treat session files as being only a little more secure than cookies, but still bad for storage of ANY sensitive information.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38346520
Thanks gr8

I have implemented all your recommendations.  Except the password and expiry ones, which are still to do.

I am getting there I think, only just got it going but will need more testing.

Off to celebrate my birthday now.

Col
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38346599
Happy birthday, Mr. B!
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38349538
Thanks!

Ok this is beginning to get to me now....below is a snippet from the top of my controller code...on refresh, the same value is shown for the otp cookie every time. It should have been destroyed, no?

Col

.........
setcookie("otp", "", time() - 3600);  // Destroy cookie.

echo "Cookie: ".$_COOKIE["otp"]."___________Random: ".rand()."<br />";
print_r($_COOKIE);
echo "<br />";
...........
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38349567
@col - hope you had a good one yesterday.

You killed the cookie but it is still in the $_COOKIE array which was created when you accessed the page.

setcookie("otp", "", time() - 3600);  // Destroy cookie.
unset($_COOKIE['otp']);

Open in new window

Here is a cookie class I use
// This is defined in the app config file
define('gbl_cookie_name', 'otp');

// Cookie class follows
define('enckey','!7$%^30&*(1)#2456@89');

class Cookie
{
  function get_cookie()
  {
    return empty($_COOKIE[gbl_cookie_name])?array(): unserialize(Cookie::_decryptCookie(enckey, $_COOKIE[gbl_cookie_name]));
  }
  
  function set_cookie(&$cookie)
  {
    // TO DO: Set  time to 0 to force cookie to expire at end of teh session - this will be linked to profile if user does not want cookies
    setcookie(gbl_cookie_name, Cookie::_encryptCookie(enckey, serialize($cookie)),(time()+60*60*24*365));
  }

  function delete_cookie()
  {
    setcookie (gbl_cookie_name, "", time()-3600);
    unset($_COOKIE[gbl_cookie_name]);
  }

  function dump()
  {
    $cookie=Cookie::get_cookie();
    print_r($cookie);
  }
  
  private function _encryptCookie($key, $value)
  {
    if(!$value){return false;}
    
    $text = $value;
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $crypttext = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_ECB, $iv);
    
    return trim(base64_encode($crypttext)); //encode for cookie
  }

  private function _decryptCookie($key, $value)
  {
    if(!$value){return false;}
    
    $crypttext = base64_decode($value); //decode cookie
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    $decrypttext = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $crypttext, MCRYPT_MODE_ECB, $iv);
    
    return trim($decrypttext);
  }  
}

Open in new window

0
 
LVL 3

Author Comment

by:colinspurs
ID: 38361249
Sry for the hiatus on this.  I am still struggling to work it out so ! have turned my attention to other areas of the site with a view to going back to it afresh.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38428451
Hi again.

After several versions I think I have got this working OK, (ie I can't break it).  But I still get extra otp's appearing in the db that are never deleted.

I attach my Word doc showing my program logic (does it look OK?), and also the latest version of access.inc.php with the userIsLoggedIn() function.  

Cheers,

  Col
The-logic-should-be.docx
access.inc.php
0
 
LVL 60

Assisted Solution

by:Julian Hansen
Julian Hansen earned 800 total points
ID: 38428611
Not sure I follow your logic but this is how I would do it

1. Render page
    i) Generate OTP and store in db something like
SELECT UUID(); 
// retrieve value
INSERT INTO otp (otp) value($otp)

Open in new window

    ii) Render form with OTP as hidden value

2. Receive login request (irrespective of where it comes from
    i) Check DB for validity
UPDATE otp SET consumed=1 WHERE otp='$otp' AND consumed=0
// AND consumed=0 is actually superfluous but will leave it there for now

Open in new window

   ii) If mysql_affected_rows == 0 OTP invalid or consumed so don't even bother
    iii) Else check login
    iv) If success then create Session / Cookie var to indicate logged in status
    v) If not regenerate form with new OTP
3. Logout
    i) Destroy cookie / session
    ii) Kill $_COOKIE array entry

I would also make your logout its own page with a redirect back to the home page once you have destroyed the session something like
<?php
session_destroy();
unset($_COOKIE['sessioncookie']);
setcookie('sessioncookie','',time()-3600);
header('location: index.php');
?>

Open in new window

0
 
LVL 3

Author Comment

by:colinspurs
ID: 38429150
I think I'm pretty much along those lines.

Rather bizarrely, as soon as I make logout its own page, the back button lets the user back in...once you've logged out.
0
 
LVL 35

Expert Comment

by:gr8gonzo
ID: 38429210
Are you certain you're still logged in and not just seeing a cached version of the page? If you go to another page that requires a logged-in user, does it work?
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38429428
I just have the one admin page, until I've got this sorted out.  Even then it may be the same physical page with different html code included depending on what they want to update.
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38429516
OK, added another page. Cannot replicate problem.  Will thrash it tomorrow.

Ta,

Col
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38430307
No, back button issue is there if logout.php is a separate page.
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38430534
When you back button to the login page and inspect element on the form - does it have the same OTP?
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38430630
I logout from the admin page which takes me (via the logout.php page) to the home page.

There is no otp cookie set, but the session_logged_in global is TRUE.  When I "back" to the admin page, this global is still TRUE which is why it lets me in I guess.

Yet logout.php should destroy it?  This is my logout.php page...

<?php
// echo "IN LOGOUT_PHP";
// die();
setcookie ("otp", "", time() - 3600);
unset($_COOKIE["otp"]);
unset($_SESSION['loggedIn']);
unset($_SESSION['userid']);
session_unset();
session_destroy();
header('location: /.' );  // Should go to home page.
?>
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38430638
OK I need Session_start()!!!
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38430653
Looks OK now apart from extra otp's in database.  Not sure where it's generating the extra ones but I will thrash again tomorrow.

Cheers,

  Col
0
 
LVL 60

Expert Comment

by:Julian Hansen
ID: 38430672
yes you do - forgot that in my script cos I was typing from memory on no sleep but you need

session_start()
session_destroy();
0
 
LVL 3

Author Comment

by:colinspurs
ID: 38432298
Okayyyyy........

More testing and dry running revealed the solution to the duplicate entries.  This is now running as well as I can hope for.

Thanks for all your help guys...just wish I had a few thousand points to work with rather than 500.

Col
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Developers of all skill levels should learn to use current best practices when developing websites. However many developers, new and old, fall into the trap of using deprecated features because this is what so many tutorials and books tell them to u…
Things That Drive Us Nuts Have you noticed the use of the reCaptcha feature at EE and other web sites?  It wants you to read and retype something that looks like this. Insanity!  It's not EE's fault - that's just the way reCaptcha works.  But it i…
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…
The viewer will learn how to count occurrences of each item in an array.
Suggested Courses
Course of the Month14 days, 8 hours left to enroll

840 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question