I'm taking my first foray into the Pyramid security module. I'm using this login code to set the auth_tkt:
#view_config(route_name='LoginForm', request_method='POST', renderer='string')
class LoginForm(SimpleObject):
def __call__(self):
emailAddress = self.request.params.get('emailAddress')
password = self.request.params.get('password')
if emailAddress != 'testemail#gmail.com' or password != 'testpassword':
errorDictionary = { 'message' : "Either the email address or password is wrong." }
self.request.response.status = 400
return json.dumps( errorDictionary, default=json_util.default)
testUserGUID = '123123123'
headers = remember(self.request, testUserGUID)
return HTTPOk(headers=headers)
It seems to work ok, but there are some puzzling details:
First of all, 2 cookies actually get set instead of one. The 2 cookies are identical (both with name "auth_tkt") except for one difference: one has a host value of ".www.mydomain.com" while the other cookie has a host value of "www.mydomain.com" Why are 2 cookies being set instead of one? What's the significance of the difference host values?
Question 2, web tools reports that neither cookie is secure. What can I do to make sure the cookie/s are secure?
Question 3: Both cookies have an expiration value of "At end of session". What does this mean and how can I customize the expiration value myself? What's the recommended practice for login cookie expiration times?
Question 4: I don't understand why the first argument of "remember" is self.request instead of self.request.response. Shouldn't the data be remembered on the response object, not the request object?
Actually, 3 cookies are generated; one without a Domain key, one with, and a 3rd with the wildcard version of your domain (the leading dot). Your browser usually either merges the two or ignores one of those (which one differs by browser, which is why 2 are set).
That last cookie is generated when the wild_domain option is set on the AuthTktAuthenticationPolicy (True by default); see the AuthTktAuthenticationPolicy API. You need this if your authentication cookie is to be shared between different subdomains (think app1.domain, app2.domain); your browser won't share cookies across subdomains without a wildcard cookie.
You need to set the secure option on your auth policy for cookies to get the secure flag set. Again, see the API.
No expiration is set, which means that the cookies are deleted when you close your browser (the end of the session your browser shows you). If you want your users to be logged out when they close the browser, leave this as the default.
Only if you want sessions to last across browser closures, set a cookie maximum age, see the max_age option in the API. This option will cause browsers to store the cookie on disk to persist between browser closures, and delete them when the maximum age has passed.
Do note that the AuthTktAuthenticationPolicy policy object can manage login sessions in a more fine-grained manner by limiting how long it'll consider any authentication cookie valid, and will let you set up a cookie refresh policy. With such a refresh policy in place users will receive new (refreshed) cookies as they continuing to use your application, but if they don't connect to your server within a set period of time, their cookie would be considered invalid and they would have to log in again.
See the timeout and reissue_time options in the API documentation for more detail on how to configure this.
The policy object requires several pieces of information from the request to be able to generate the cookies, not least of all the host name of your server.
Related
I'm using Flask builtin session mecanism.
Here is my understanding of session mecanism (with flask) :
all session data are stored in a signed cookie (with app.secret_key)
when a session data is modified, the cookie is changed
session's data are protected against write client side (due to signature) but not against read
Imagine the following scenario :
In my session I put a variable try_number=3
Each time the user make an action, a decrease this number
If this number is equal to 0, action is forbidden
The user connect to the application for the first time, the application send a Set-Cookie: sesssion=Flask.sign("try_number=3"), let's call this cookie COOKIE_A.
The user perform his first action, he send COOKIE_A, the application reply with Set-Cookie: sesssion=Flask.sign("try_number=2"), let's call this cookie COOKIE_B.
Now, if the user perform another action, but doesn't use COOKIE_B but COOKIE_A again (using curl for exemple), the cookie is still signed, and will be handled by the server, with try_number=3.
Therefore, only using the COOKIE_A for all operation, he will be able to "spoof" session mecanism, and make unlimited action with the same session.
Is there any builtin mecanism to prevent this ?
(I'm not talking about snippet for using sqlite / redis, but builtin solution)
This is not a failure of the security of Flask's cookies, it's a failure of your counter design. There is no built in protection against replay attacks.
You can shorten the expiration time of the session cookie. This doesn't really solve the problem, it just makes the window smaller. It also makes the session inconvenient for regular use, which would annoy your normal users.
Ultimately, you'll have to store some information on the server and check against it. You could send a nonce with every request and keep a store of which ones have been sent back, ignoring ones that have been seen before. You could also just store all session information (except some identifying key) on the server side, so it can't be re-sent.
If app.secret_key isn't set, Flask will not allow you to set or access the session dictionary.
This is all that the flask user guide has to say on the subject.
I am very new to web development and I have no idea how/why any security stuff works. I would like to understand what Flask is doing under the hood.
Why does Flask force us to set this secret_key property?
How does Flask use the secret_key property?
The answer below pertains primarily to Signed Cookies, an implementation of the concept of sessions (as used in web applications). Flask offers both, normal (unsigned) cookies (via request.cookies and response.set_cookie()) and signed cookies (via flask.session). The answer has two parts: the first describes how a Signed Cookie is generated, and the second is presented as a series of Question/Answer that address different aspects of the scheme. The syntax used for the examples is Python3, but the concepts apply also to previous versions.
What is SECRET_KEY (or how to create a Signed Cookie)?
Signing cookies is a preventive measure against cookie tampering. During the process of signing a cookie, the SECRET_KEY is used in a way similar to how a "salt" would be used to muddle a password before hashing it. Here's a (widely) simplified description of the concept. The code in the examples is meant to be illustrative. Many of the steps have been omitted and not all of the functions actually exist. The goal here is to provide a general understanding of the main idea, but practical implementations will likely be a bit more involved. Also, keep in mind that Flask already provides most of this for you in the background. So, besides setting values to your cookie (via the session API) and providing a SECRET_KEY, it's not only ill-advised to re-implement this yourself, but there's no need to do so:
A poor man's cookie signature
Before sending a Response to the browser:
( 1 ) First a SECRET_KEY is established. It should only be known to the application and should be kept relatively constant during the application's life cycle, including through application restarts.
# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')
( 2 ) create a cookie
>>> cookie = make_cookie(
... name='_profile',
... content='uid=382|membership=regular',
... ...
... expires='July 1 2030...'
... )
>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
...
...
expires: July 1 2030, 1:20:40 AM UTC
( 3 ) to create a signature, append (or prepend) the SECRET_KEY to the cookie byte string, then generate a hash from that combination.
# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....
( 4 ) Now affix the signature at one end of the content field of the original cookie.
# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9... <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
and that's what is sent to the client.
# add cookie to response
>>> response.set_cookie(cookie)
# send to browser -->
Upon receiving the cookie from the browser:
( 5 ) When the browser returns this cookie back to the server, strip the signature from the cookie's content field to get back the original cookie.
# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)
( 6 ) Use the original cookie with the application's SECRET_KEY to recalculate the signature using the same method as in step 3.
# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
( 7 ) Compare the calculated result with the signature previously popped out of the just received cookie. If they match, we know that the cookie has not been messed with. But if even just a space has been added to the cookie, the signatures won't match.
# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature
( 8 ) If they don't match then you may respond with any number of actions, log the event, discard the cookie, issue a fresh one, redirect to a login page, etc.
>>> if not good_cookie:
... security_log(cookie)
Hash-based Message Authentication Code (HMAC)
The type of signature generated above that requires a secret key to ensure the integrity of some contents is called in cryptography a Message Authentication Code or MAC.
I specified earlier that the example above is an oversimplification of that concept and that it wasn't a good idea to implement your own signing. That's because the algorithm used to sign cookies in Flask is called HMAC and is a bit more involved than the above simple step-by-step. The general idea is the same, but due to reasons beyond the scope of this discussion, the series of computations are a tad bit more complex.
If you're still interested in crafting a DIY, as it's usually the case, Python has some modules to help you get started :) here's a starting block:
import hmac
import hashlib
def create_signature(secret_key, msg, digestmod=None):
if digestmod is None:
digestmod = hashlib.sha1
mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
return mac.digest()
The documentaton for hmac and hashlib.
The "Demystification" of SECRET_KEY :)
What's a "signature" in this context?
It's a method to ensure that some content has not been modified by anyone other than a person or an entity authorized to do so.
One of the simplest forms of signature is the "checksum", which simply verifies that two pieces of data are the same. For example, when installing software from source it's important to first confirm that your copy of the source code is identical to the author's. A common approach to do this is to run the source through a cryptographic hash function and compare the output with the checksum published on the project's home page.
Let's say for instance that you're about to download a project's source in a gzipped file from a web mirror. The SHA1 checksum published on the project's web page is 'eb84e8da7ca23e9f83....'
# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....
Both hashes are the same, you know that you have an identical copy.
What's a cookie?
An extensive discussion on cookies would go beyond the scope of this question. I provide an overview here since a minimal understanding can be useful to have a better understanding of how and why SECRET_KEY is useful. I highly encourage you to follow up with some personal readings on HTTP Cookies.
A common practice in web applications is to use the client (web browser) as a lightweight cache. Cookies are one implementation of this practice. A cookie is typically some data added by the server to an HTTP response by way of its headers. It's kept by the browser which subsequently sends it back to the server when issuing requests, also by way of HTTP headers. The data contained in a cookie can be used to emulate what's called statefulness, the illusion that the server is maintaining an ongoing connection with the client. Only, in this case, instead of a wire to keep the connection "alive", you simply have snapshots of the state of the application after it has handled a client's request. These snapshots are carried back and forth between client and server. Upon receiving a request, the server first reads the content of the cookie to reestablish the context of its conversation with the client. It then handles the request within that context and before returning the response to the client, updates the cookie. The illusion of an ongoing session is thus maintained.
What does a cookie look like?
A typical cookie would look like this:
name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
Cookies are trivial to peruse from any modern browser. On Firefox for example go to Preferences > Privacy > History > remove individual cookies.
The content field is the most relevant to the application. Other fields carry mostly meta instructions to specify various scopes of influence.
Why use cookies at all?
The short answer is performance. Using cookies, minimizes the need to look things up in various data stores (memory caches, files, databases, etc), thus speeding things up on the server application's side. Keep in mind that the bigger the cookie the heavier the payload over the network, so what you save in database lookup on the server you might lose over the network. Consider carefully what to include in your cookies.
Why would cookies need to be signed?
Cookies are used to keep all sorts of information, some of which can be very sensitive. They're also by nature not safe and require that a number of auxiliary precautions be taken to be considered secure in any way for both parties, client and server. Signing cookies specifically addresses the problem that they can be tinkered with in attempts to fool server applications. There are other measures to mitigate other types of vulnerabilities, I encourage you to read up more on cookies.
How can a cookie be tampered with?
Cookies reside on the client in text form and can be edited with no effort. A cookie received by your server application could have been modified for a number of reasons, some of which may not be innocent. Imagine a web application that keeps permission information about its users on cookies and grants privileges based on that information. If the cookie is not tinker-proof, anyone could modify theirs to elevate their status from "role=visitor" to "role=admin" and the application would be none the wiser.
Why is a SECRET_KEY necessary to sign cookies?
Verifying cookies is a tad bit different than verifying source code the way it's described earlier. In the case of the source code, the original author is the trustee and owner of the reference fingerprint (the checksum), which will be kept public. What you don't trust is the source code, but you trust the public signature. So to verify your copy of the source you simply want your calculated hash to match the public hash.
In the case of a cookie however the application doesn't keep track of the signature, it keeps track of its SECRET_KEY. The SECRET_KEY is the reference fingerprint. Cookies travel with a signature that they claim to be legit. Legitimacy here means that the signature was issued by the owner of the cookie, that is the application, and in this case, it's that claim that you don't trust and you need to check the signature for validity. To do that you need to include an element in the signature that is only known to you, that's the SECRET_KEY. Someone may change a cookie, but since they don't have the secret ingredient to properly calculate a valid signature they cannot spoof it. As stated a bit earlier this type of fingerprinting, where on top of the checksum one also provides a secret key, is called a Message Authentication Code.
What about Sessions?
Sessions in their classical implementation are cookies that carry only an ID in the content field, the session_id. The purpose of sessions is exactly the same as signed cookies, i.e. to prevent cookie tampering. Classical sessions have a different approach though. Upon receiving a session cookie the server uses the ID to look up the session data in its own local storage, which could be a database, a file, or sometimes a cache in memory. The session cookie is typically set to expire when the browser is closed. Because of the local storage lookup step, this implementation of sessions typically incurs a performance hit. Signed cookies are becoming a preferred alternative and that's how Flask's sessions are implemented. In other words, Flask sessions are signed cookies, and to use signed cookies in Flask just use its Session API.
Why not also encrypt the cookies?
Sometimes the contents of cookies can be encrypted before also being signed. This is done if they're deemed too sensitive to be visible from the browser (encryption hides the contents). Simply signing cookies however, addresses a different need, one where there's a desire to maintain a degree of visibility and usability to cookies on the browser, while preventing that they'd be meddled with.
What happens if I change the SECRET_KEY?
By changing the SECRET_KEY you're invalidating all cookies signed with the previous key. When the application receives a request with a cookie that was signed with a previous SECRET_KEY, it will try to calculate the signature with the new SECRET_KEY, and both signatures won't match, this cookie and all its data will be rejected, it will be as if the browser is connecting to the server for the first time. Users will be logged out and their old cookie will be forgotten, along with anything stored inside. Note that this is different from the way an expired cookie is handled. An expired cookie may have its lease extended if its signature checks out. An invalid signature just implies a plain invalid cookie.
So unless you want to invalidate all signed cookies, try to keep the SECRET_KEY the same for extended periods.
What's a good SECRET_KEY?
A secret key should be hard to guess. The documentation on Sessions has a good recipe for random key generation:
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
You copy the key and paste it in your configuration file as the value of SECRET_KEY.
Short of using a key that was randomly generated, you could use a complex assortment of words, numbers, and symbols, perhaps arranged in a sentence known only to you, encoded in byte form.
Do not set the SECRET_KEY directly with a function that generates a different key each time it's called. For example, don't do this:
# this is not good
SECRET_KEY = random_key_generator()
Each time your application is restarted it will be given a new key, thus invalidating the previous.
Instead, open an interactive python shell and call the function to generate the key, then copy and paste it to the config.
Anything that requires encryption (for safe-keeping against tampering by attackers) requires the secret key to be set. For just Flask itself, that 'anything' is the Session object, but other extensions can make use of the same secret.
secret_key is merely the value set for the SECRET_KEY configuration key, or you can set it directly.
The Sessions section in the Quickstart has good, sane advice on what kind of server-side secret you should set.
Encryption relies on secrets; if you didn't set a server-side secret for the encryption to use, everyone would be able to break your encryption; it's like the password to your computer. The secret plus the data-to-sign are used to create a signature string, a hard-to-recreate value using a cryptographic hashing algorithm; only if you have the exact same secret and the original data can you recreate this value, letting Flask detect if anything has been altered without permission. Since the secret is never included with data Flask sends to the client, a client cannot tamper with session data and hope to produce a new, valid signature.
Flask uses the itsdangerous library to do all the hard work; sessions use the itsdangerous.URLSafeTimedSerializer class with a customized JSON serializer.
I use flask session in my app. In one of my handler I set session value and no session set in other handlers. But I found that in every response there is a http header: Set Cookie exists. Why does that happen?
app = Flask(__name__)
app.secret_key = r"A0Zr98j/3yX R~XHH!jmN'LWX/,?RT"
#app.route('/auth/login', methods=['POST'])
#crossdomain(origin='*')
def authlogin():
user = User(username=username, registered_at=sqlnow())
user.accounts = [Account(provider='weibo', access_token=access_token, uid=uid)]
account = user.accounts[0]
session['user_id'] = account.user_id
return jsonify({
'status': 'success',
'data': {
'user_id': account.user_id,
'uid': account.uid
}
})
#app.route('/api/movies/<movie_type>')
def moviescoming(movie_type):
if movie_type == 'coming':
return getmovies(MOVIE_TYPE_PLAYING, offset, limit)
else:
return getmovies(MOVIE_TYPE_COMING, offset, limit)
app.run(host='0.0.0.0', debug=True)
Code shows here:
https://github.com/aisensiy/dianying/blob/master/index.py
The Short Answer:
This is by design, but a recent change in Flask allows you to change this behavior through use of the SESSION_REFRESH_EACH_REQUEST option. As of the posting of this answer, that option is not yet in a stable release of Flask.
The Long Answer
Let's back up and discuss how cookies are supposed to work to begin with:
Cookies as a Standard
RFC 6265 defines that a cookie should expire when the agent (the browser) declares the session closed (typically, when the browser is closed), unless there was provided some mechanism to tell the browser when the cookie should actually expire:
Unless the cookie's attributes indicate otherwise, the cookie [...]
expires at the end of the current session (as defined by the user
agent).
[...]
If a cookie has neither the Max-Age nor the Expires attribute, the
user agent will retain the cookie until "the current session is over"
(as defined by the user agent).
If the server wishes a cookie to survive an agent restart, they need to set an expiration. Note that the Expires attribute is typically preferred due to the fact that Internet Explorer has a history of poor support for max-age.
Creating Permanent Cookies
So, it's impossible to say that a cookie should be "permanent". When people talk about a "permanent" cookie, what they really are talking about is a cookie that survives a browser restart. There are two strategies that I know of for creating this "permanent" cookie:
Set the cookie's expiration to something that is good enough to be considered permanent (such as the year 9999).
Set the cookie's expiration to something relatively recent in the future (e.g., 31 days), but every time the cookie is used update the expiration again. For example, on January 1st we will set the cookie to expire on February 1st, but then when the user uses the cookie on January 2nd we are updating the cookie (by using Set-Cookie) to have it expire on February 2nd.
The first method requires the Set-Cookie header to only be set to the client once (unless the cookie contents need to change).
The second method would require the Set-Cookie header to be sent with every update so that the expiration is constantly "pushed off" as the user continues to use the service. Note that it also isn't really "permanent", as a user that does not use your site for over 31 days will have their cookie expire.
RFC 6265 does have a bit to say on defining the expiration date:
Although servers can set the expiration date for cookies to the
distant future, most user agents do not actually retain cookies for
multiple decades. Rather than choosing gratuitously long expiration
periods, servers SHOULD promote user privacy by selecting reasonable
cookie expiration periods based on the purpose of the cookie. For
example, a typical session identifier might reasonably be set to
expire in two weeks.
So, while it doesn't explicitly say whether or not to be constantly updating the expiration date, it does seem to say that using a far-future date should NOT be considered a good practice.
Flask's Implementation of "Permanent Cookies"
Flask uses the second method (constantly updating the cookie expiration with Set-Cookie) by design. By default, the expiration of the cookie will be 31 days in the future (configurable by PERMANENT_SESSION_LIFETIME). With every request, Flask will use another Set-Cookie to push the expiration out another 31 days (or whatever you set your permanent session lifetime value to). Therefore, the Set-Cookie on every request you see is expected, even if the session has not changed.
Recently, however, there has been a discussion in a pull request regarding using Set-Cookie only when the cookie changes. This resulted in a new feature that allows the user to change how this works. Flask will continue to work as it has, but the user can set a new SESSION_REFRESH_EACH_REQUEST option to False, which will cause the Set-Cookie header to only be sent when the cookie changes.
The new item is documented as:
this flag controls how permanent sessions are refresh [sic]. If set to
True (which is the default) then the cookie is refreshed each
request which automatically bumps the lifetime. If set to False a
set-cookie header is only sent if the session is modified. Non
permanent sessions are not affected by this.
This new option, together with the existing PERMANENT_SESSION_LIFETIME, allows Flask developers to better tune exactly how their "permanent" cookies will be set to expire.
As of this answer's posting date (December 24th, 2013), the SESSION_REFRESH_EACH_REQUEST option has not been part of any Flask release, and therefore users wishing to use it will need to wait for a future Flask release.
I've a Flask application, served with Nginx+WSGI (FastCGI & Gevent) and use standard Flask sessions. I do not use the session.permanent=True or any other extra option, but simply set SECRET_KEY in the default configuration.
I do not save any (key,value) pairs in the session, and only rely on the SID = session['_id'] entry to identify a returning user. I use the following code the read the SID:
#page.route ('/')
def main (page='home', template='index.html'):
if not request.args.get ('silent', False):
print >> sys.stderr, "Session ID: %r" % session['_id']
I made the following observations:
For same IP addresses, but different browsers I get different SIDs - that's expected;
For different IPs & same browser I again have different SIDs - expected;
For same IP address with same browser I get same SID - also expected;
Now, point (3) is interesting because even if a delete the corresponding cookie the SID remains constant! To some extent even that might be understandable, but actually I was expecting the SID to change between different cookies. But the only difference I see is that
session.new is True
for the first request immediately after the deletion of the cookie. Even that is very much expected; but given these facts I face the following problems:
Does this mean that for different users sitting behind the same IP (with the same browser configuration) my back-end will mistake them for the same user?
If point (1) is not the case, the current behavior of these "sticky" sessions is actually quite pleasant, since this avoids the situation where my users might loose there data just because they deleted the corresponding cookie.
They can still save the day, by revisiting the site from the same network with the same browser. I like that, but only if point (1) is not the case.
I assume point (1) will actually bite me, would the conclusion actually be to save a token in the session and hence accept the fate that the user can blow himself up, by simply deleting his cookie?
Or is there a way to tell Flask to give different SIDs for each fresh cookie?
Actually, this question arouse since I used a load impact service, which was simulating different users (on the same IP) but my back-end kept seeing them as a single user since the corresponding SIDs were all the same.
The application is available for tests at http://webed.blackhan.ch (which upon release will move the https://notex.ch [a browser based text editor]). Thank you for your answers.
It looks like you're using the Flask-Login extension. Here's the code that generates the id token:
def _create_identifier():
base = unicode("%s|%s" % (request.remote_addr,
request.headers.get("User-Agent")), 'utf8', errors='replace')
hsh = md5()
hsh.update(base.encode("utf8"))
return hsh.digest()
It's basically just md5(ip_address + user_agent).
Flask uses Werkzeug's secure cookies to store this identifier. Secure cookies are (as their name suggests) secure:
This module implements a cookie that is not alterable from the client because it adds a checksum the server checks for. You can use it as session replacement if all you have is a user id or something to mark a logged in user.
session['_id'] is not an actual session identifier. It's just a value used by Flask-Login to implement Session Protection.
Standard Flask sessions do not have an SID - as it would serve no purpose since the actual content of the session is stored in the cookie itself. Also see this.
it's now 2022, and Flask-Session does support session.sid to get a generated UUID that looks something like this:
print(session.sid)
>>> f9c792fa-70e0-46e3-b84a-3a11813468ce
From the docs (https://flasksession.readthedocs.io/en/latest/)
sid
Session id, internally we use uuid.uuid4() to generate one session id. You can access it with session.sid.
I'm using pyramid web framework. I was confused by the relationship between the cookie and session. After looked up in wikipedia, did I know that session is an abstract concept and cookie may just be an kind of approach (on the client side).
So, my question is, what's the most common implementation (on both the client and server)? Can somebody give some example (maybe just description) codes? (I wouldn't like to use the provided session support inside the pyramid in order to learn)
The most common implementation of sessions is to use a cookie.
A cookie provides a way to store an arbitrary piece of text, which is usually used as a session identifier. When the cookie gets sent along with a HTTP request, the server (technically the code running on it) can use the cookie text (if it exists) to recognise that it has seen a client before. Text in a cookie usually provides enough information to retrieve extra information from the database about this client.
For example, a very naive implementation might store the primary key to the shopping_cart table in a database, so that when the server receives the cookie text it can directly use it to access the appropriate shopping cart for that particular client.
(And it's a naive approach because a user can do something like change their own cookie to a different primary key and access someone else's cart that way. Choosing a proper session id isn't as simple as it seems, which is why it's almost always better to use an existing implementation of sessions.)
An alternate approach is to store a session identifier is to use a GET parameter in the url (for example, in something like http://example.com/some/page?sid=4s6da4sdasd48, then the sid GET param serves the same function as the cookie string). In this approach, all links to other pages on the site have the GET param appended to them.
In general, the cookie stored with the client is just a long, hard-to-guess hash code string that can be used as a key into a database. On the server side, you have a table mapping those session hashes to primary keys (a session hash should never be a primary key) and expiration timestamps.
So when you get a request, first thing you do is look for the cookie. If there isn't one, create a session entry (cookie + expiration timestamp) in the database table. If there is one, look it up and make sure it hasn't expired; if it has, make a new one. In either case, if you made a new cookie, you might want to pass that fact down to later code so it knows if it needs to ask for a login or something. If you didn't need to make a new cookie, reset the expiration timestamp so you don't expire the session too soon.
While handling the view code and generating a response, you can use that session primary key to index into other tables that have data associated with the session. Finally, in the response sent back to the client, set the cookie to the session key hash.
If someone has cookies disabled, then their session cookie will always be new, and any session-based features won't work.
A session is (usually) a cookie that has a unique value. This value maps to a value in a database or held in memory that then tells you what session to load. PHP has an alternate method where it appends a unique value to the end of every URL (if you've ever seen PHPSESSID in a URL you now know why) but that has security implications (in theory).
Of course, since cookies are sent back and forth with every request unless you're talking over HTTPS you are sending the only way to know (reliably) that the client you are talking to now is the same one you logged in ten seconds ago to anyone on the same wireless network. See programs like Firesheep for reasons why switching to HTTPS is a good idea.
Finally, if you do want to build your own I, was given some advice on the matter by a university professor. Give out a new token on every page load and invalidate all a users tokens if an invalid token is used. This just means that if an attacker does get a token and logs in to it whilst it is still valid when the victim clicks a link both parties get logged out.