an authenticated identity service

david nicol

YAPC/America 2002

Herein is described a working multiple-step protocol for sharing a single sign-on identity among a family of web services, with working sample implementation of both server and client parts.

Features include:


Some definitions:

web server: a *BSD box running Boa, or equivalent

cookie: see http://www.netscape.com/newsref/std/cookie_spec.html

web service: an infrastructure-level feature of the developing "world wide web"

AIS: Authenticated Identity Service

User: an entity, and their machinery, wanting to use a web service that uses AIS

AIS server: a web server providing this AIS service

AIS server resource identifier (AISSRI) : a character string to which AIS server request types can be postpended to obtain AIS services, such as "http://www.pay2send.com/cgi/AIS/"

SSO: single sign-on. Establish the user's identity to the satisfaction of the AIS server.

Session: maintained by a particular service, or AIS client. 


Time and state diagrams

There are three participants in the operation of AIS: the user, the AIS client, and the AIS server. The states described in the state columns of the table below indicate the state at the completion of the step described in that row.

Step zero, in which the user demonstrates their identity to the AIS server, can be replaced by any other single-sign-on method, such as web server infrastructures that request log-in credentials and provide a HTTP_USER variable in the CGI environment.

Create SSO session
step description User AIS Client AIS Server user state AIS Client state AIS Server state
0.0 before no SSO session no mappings for user
0.1 single-sign-on request SSO session no SSO session no mappings for user
0.2 single sign-on map identity to v-code, issue v-code no SSO session v-code mapping WRT this user
0.3 single sign-on return v-code v-code mapping WRT this user
0.4 single sign-on map identity to SSO session cookie and issue it no SSO session v-code and cookie mappings 
0.5 single sign-on receive and store AIS SSO cookie SSO session (persists) v-code and cookie mappings 

In response to a request for a restricted resource made outside of a session, the AIS client web service directs user to request a single-use identification key.
step description User AIS Client
1.1 resource access attempt Issue a request for a protected resource
1.2 No session!   redirect user to AIS server for validation
  • use HTTP redirection
  • send the user to the AIS server
  • AIS server will append the identification key the QUERY_STRING in step 3
  • include enough information for the server to send the user back 
  • for instance: 

  • Location: ${AISSRI}present?http://$ENV{HTTP_HOST}$ENV{REQUEST_URI}&C=

User requests single-use identification key
step description User AIS Server
2 request key for self follows HTTP redirect to AIS server listens at appropriate port for such things

AIS generates key and directs user back to web service
step description User AIS Server
3.1 AIS server generates single use key  
  • look up user's session cookie to find user identity (or fail)
  • generate random unique key
  • associate user identity with key
  • go through motions if there is no user
  • redirect the user back to where they are asking to have the key sent
  • for instance: 

  • Location: $ENV{QUERY_STRING}${KEY}
  • Log request, associated with user
3.2   follow redirect  

web service presents AIS with key
step description User AIS Client AIS Server
4.1 key gets to web-service follows second redirect listens listens
4.2 handles extended URL   use socket library or LWP::get or something to access AIS server "query" function with the provided key listens

AIS provides user identity to web service
step description User AIS client AIS server
5.1 AIS server responds to AIS client not involved in this step accepts reply sends an AIS-XML reply block

at this point, the web service can create its own session object and cookie the user with a key associating them with it. The AIS client can use the key the AIS server produced, or a new key -- this is between the AIS client and it's users only.

AIS-XML example:

Content-Type: text/plain

<?xml version="1.0" encoding="ISO-8859-1"?>
<aisresponse>
<identity>davidnicol@pay2send.com</identity>
<aissri>http://www.pay2send.com/cgi/ais/</aissri>
<user_remote_addr>65.26.97.7</user_remote_addr>
</aisresponse>
"NULL" is reserved as the identity provided when there was no key.

 "ERROR" is reserved as an identity to indicate that there is a problem requiring intervention, such as the AIS client's owner has not paid their dues to whoever is hosting this particular AIS service.

 Implementations may reserve other identities, or extend AIS-XML to include more data.


implementing an AIS server

Assuming your AIS server is already part of a SSO domain of some kind, the AIS server can be implemented with only two functions, "present" to trigger generation of a new key and "query" to offer authenticated identities in exchange for keys. The following example uses dbmopen to store all types of data persistently in a single database.

A revised suite of AIS server programs using three databases may be available from the web page by the time you read this.

#!/usr/local/bin/perl -I.
=pod
this is a sample AIS "present" program, which
reads a SSO session certificate from a cookie
called AIS_Session and uses DirDB
for data persistence.

If you are installing this somewhere where you
already have a HTTP_USER environment variable,
just use that instead if you want, to determine
what user is presenting; but you still need to
store the mapping back from the single-use key
somewhere.
=cut


# -d 'data' or mkdir 'data', 0777 or die "could not create data directory";
# use Fcntl ':flock'; # import LOCK_* constants
# open LOCK,'>>data/AIS_lock' or die 'Cannot open Lock File';
# flock LOCK, LOCK_EX;
# dbmopen(%DATA,'data/AIS_data',0660); # so much easier to write than "tie..."

use DirDB;
tie %Sessions, 'DirDB', 'data/Sessions';
tie %OTU_keys, 'DirDB', 'data/OTU_keys';

# Determine Identity from Cookies:
my $Ses_key;

$ENV{HTTP_COOKIE} =~ m/AIS_Session=(\w+)/ and $Ses_key = $1;

=pod

The single-sign-on keys are set elsewhere; the AIS client
is responsible for directing NULL users to a log-in page
or something like that -- logging people in is not the
"present" script's problem

=cut

my $Identity = $Ses_key ? $Sessions{$Ses_key} : 'NULL';
my $Single_Use_Key = join('',time,(map {("A".."Z")[rand 26]} (0..19)), $$);

# remember identity under the single-use-key:
$OTU_keys{$Single_Use_Key} = <<IDENTITYBLOCK;
<identity>$Identity</identity>
<aissri>http://$ENV{SERVER_NAME}/cgi/ais/</aissri>
<user_remote_addr>$ENV{REMOTE_ADDR}</user_remote_addr>
IDENTITYBLOCK

# send our user back to the AIS client
print "Location: $ENV{QUERY_STRING}$Single_Use_Key\n\n";

# every twenty present runs, clean up OTU keys older than two minutes
unless ($$ % 20){

        close STDOUT;
        delete @OTU_keys{grep {(time - $_) > 120 } keys %OTU_keys};


};

exit;
__END__

The previous program, "present," creates single-use keys and maps identities to them. To get the identity back, a program called "query" will provide the identity mapped to a single-use key.
#!/usr/local/bin/perl
=pod
this is a sample AIS "query" program, which
looks up single-use keys provided in its query string and
replies with a block of AIS-XML.
=cut

use DirDB;

tie %OTU, 'DirDB', './data/OTU_keys';

my $xmlblock =  $OTU{$ENV{QUERY_STRING}} || <<DEFAULT;
<identity>ERROR</identity>
<error>provided single use key not found in AIS data</error>
<aissri>http://$ENV{SERVER_NAME}/cgi/ais/</aissri>
<user_remote_addr>$ENV{REMOTE_ADDR}</user_remote_addr>
DEFAULT

delete $OTU{$ENV{QUERY_STRING}}; # anyone know how to
                                            # incorporate return-by-delete
                                            # into the previous statement?


print <<EOF and exit;
Content-Type: text/plain

<?xml version="1.0" encoding="ISO-8859-1"?>
<aisresponse>
$xmlblock</aisresponse>
EOF

__END__

With the above two programs in place, it is possible to issue a request for, say, http://pay2send.com/cgi/ais/present?http://pay2send.com/cgi/ais/query? and get an XML page.

To have this system be good for anything requires a single-sign-on realm of some kind, and then some AIS client software. I present below


the ais/add program looks for a CGI variable "email" and creates a single-sign-on identity mapping for the e-mail address given, and e-mails the mapping code to the address. Without such a variable, it displays a log-in page.
#!/usr/local/bin/perl -I.
=pod
this is a sample AIS "add" program, which
will add a sign-on identity to its database or
display a log-in page if no "email" variable appears
in the CGI data.
=cut

# look for "email" in CGI data
$rawdata = join '', $ENV{QUERY_STRING}, <STDIN>;
($email) = ($rawdata =~ m/email=([^&]+)/);
$email =~ s/%(..)/chr(hex($1))/ge;

# look for an e-mail address
($email) = ($email =~ m/([^\s\<\>\@\|]+\@[^\s\<\>\@\|]+)/);

unless ($email){
        print <<EOF;
Content-Type: text/html

<title>AIS log-in page</title>
<body bgcolor=ffffff>
<form method=POST action="$ENV{SCRIPT_NAME}">
What is a good e-mail address for you?
<input type=text name="email">
<input type=submit value="send me a log-in key">
</form>

</body>

EOF
        exit;
};

use DirDB; # a concurrent-write-safe database :)
tie %DATA,'DirDB','data/SSO_keys'; 

my $SSO_key = join '',time,(map {("A".."Z")[rand 26]} (0..15)), $$;

$DATA{$SSO_key} = $email;


        open(MAIL,"|sendmail -t -i -f 'AIS-bounce-recipient\@$ENV{SERVER_NAME}'");
        print MAIL <<EOF;
To: <$email>
From: AIS-autoresponder\@$ENV{SERVER_NAME}
X-Abuse-To: (tracert $ENV{HTTP_ADDR})\@abuse.net 
Subject: AIS LOGIN LINKS for $email
Content-Type: text/html

<body bgcolor=ffffff>

<form method=POST action="http://$ENV{SERVER_NAME}/cgi/ais/login">
<input type=hidden name="SK" value="$SSO_key">
To log your web browser into the single sign-on service
at $ENV{SERVER_NAME}/cgi/ais/,
<input type=submit value="click here">
<p>

To log your web browser out, click here:<p>

<a href="http://$ENV{SERVER_NAME}/cgi/ais/logout">
http://$ENV{SERVER_NAME}/cgi/ais/logout
</a>
<p>

To disable the log-in button in this message click here:<p>

<a href="http://$ENV{SERVER_NAME}/cgi/ais/delete?$SSO_key">
http://$ENV{SERVER_NAME}/cgi/ais/delete?$SSO_key
</a>

<p>
You appear to have requested this log-in link while using
a $ENV{HTTP_USER_AGENT} web browser from IP address $ENV{REMOTE_ADDR}

<p>

</body>

EOF

        print <<EOF;
Content-Type: text/html

<body bgcolor=ffffff>

You are connecting from $ENV{REMOTE_ADDR}<p>

A message containing an AIS log-in button has been e-mailed to &lt;$email&gt;

</body>

EOF


__END__




#!/usr/local/bin/perl -I.

=pod
this is a sample AIS "login" program, which
handles the login buttons sent out by the "add" program.
=cut

# look for "SK" in CGI data
$rawdata = join '', $ENV{QUERY_STRING}, <STDIN>;
($SK) = ($rawdata =~ m/SK=([^&]+)/);

print STDERR "have SK and it is <$SK>\n";

use DirDB; # a concurrent-write-safe database :)
tie %SSO,'DirDB','data/SSO_keys'; 
tie %Ses,'DirDB','data/Sessions'; 

my $SessionCookie = join '',time,(map {("A".."Z")[rand 26]} (0..25)), $$;

my $email;

unless ($email = $SSO{$SK}){
        print <<EOF and exit;
Content-Type: text/html

<body bgcolor=ffffff>

Sorry, but the AIS login button you have clicked refers to
a single sign-on capability key that either does not exist or
has been deleted. <p>

<form method=POST action="/cgi/ais/add">
What is a good e-mail address for you?
<input type=text name="email">
<input type=submit value="send me a new log-in button">
</form>

</body>


EOF

};

$Ses{$SessionCookie} = $email;


open(MAIL,"|sendmail -t -i -f 'AIS-bounce-recipient\@$ENV{SERVER_NAME}'");
print MAIL <<EOF;
To: <$email>
From: AIS-autoresponder\@$ENV{SERVER_NAME}
X-Abuse-To: (tracert $ENV{HTTP_ADDR})\@abuse.net
Subject: AIS LOGIN receipt for $email

You have logged in to the $ENV{SERVER_NAME} AIS service
 from IP address $ENV{REMOTE_ADDR}
 using a $ENV{HTTP_USER_AGENT} web browser.

EOF

print <<EOF;
Set-Cookie: AIS_Session=$SessionCookie; path=/cgi/ais/;
Content-Type: text/html

<body bgcolor=ffffff>

Welcome (back)! You are connecting from $ENV{REMOTE_ADDR}<p>

A message recording this AIS log-in has been e-mailed to &lt;$email&gt;<p>

Unless you have disabled cookies, you now are authenticable via the
AIS service at $ENV{SERVER_NAME}.

Please use your browser's BACK and RELOAD features to return to the page
you are trying to access, if any.<p>

</body>

EOF

__END__




#!/usr/local/bin/perl -I.

=pod
this is a sample AIS "logout" program, which
handles the login buttons sent out by the "add" program.
=cut


my ($SessionCookie) = ($ENV{HTTP_COOKIE} =~ /AIS_Session=(\w+)/);

unless ($SessionCookie){
        print <<EOF and exit;
Content-Type: text/html

<body bgcolor=ffffff>

You appear to be already logged out

</body>


EOF

};

use DirDB;
tie %Ses,'DirDB','data/Sessions'; 

delete $Ses{$SessionCookie};


print <<EOF;
Set-Cookie: AIS_Session=; path=/cgi/ais/;
Content-Type: text/html

<body bgcolor=ffffff>

Your AIS session cookie has been deleted.  You may log in again
by clicking on the log-in button in your e-mail.


</body>

EOF

close STDOUT;

# clean up sessions more than 12 hours old, if any
delete @Ses{grep {(time - $_) > 12 * 3600} keys %Ses};


__END__








#!/usr/local/bin/perl -I.

=pod
this is a sample AIS "delete" program, which
disables the log-in buttons sent out by the "add" program.
=cut

use DirDB; # a concurrent-write-safe database :)
tie %DATA,'DirDB','data/SSO_keys'; 


my $email = $DATA{$ENV{QUERY_STRING}};
unless ( $ENV{QUERY_STRING} and $email ){
        print <<EOF and exit;
Content-Type: text/html

<body bgcolor=ffffff>

Sorry, but the AIS login button you are trying to disable refers to
a single sign-on capability key that either does not exist or
has been deleted already. <p>

</body>

EOF

};

delete  $DATA{$ENV{QUERY_STRING}};

open(MAIL,"|sendmail -t -i -f 'AIS-bounce-recipient\@$ENV{SERVER_NAME}'");
print MAIL <<EOF;
To: <$email>
From: AIS-autoresponder\@$ENV{SERVER_NAME}
X-Abuse-To: (tracert $ENV{HTTP_ADDR})\@abuse.net
Subject: AIS DELETE receipt for $email

You have connected to the $ENV{SERVER_NAME} AIS service
 from IP address $ENV{HTTP_ADDR}
 using a $ENV{HTTP_USER_AGENT} web browser to delete
 the single-sign-on key $ENV{QUERY_STRING}

EOF

print <<EOF;
Content-Type: text/html

<body bgcolor=ffffff>

You are connecting from $ENV{REMOTE_ADDR}<p>

A message recording this AIS deletion has been e-mailed to <$email><p>

AIS login key $ENV{QUERY_STRING} has been deleted.

</body>

EOF

__END__






To be an AIS client, a web service needs to have permission to access the AIS server. Some AIS servers, such as the sample ones here, are public, but others may have subscription or membership access models.



package CGI::AIS::Session;

 use strict;

use vars qw{ *SOCK @ISA @EXPORT $VERSION };

require Exporter;

 @ISA = qw(Exporter);
 @EXPORT = qw(Authenticate);

 $VERSION = '0.01';

use Carp;


use Socket qw(:DEFAULT :crlf);
use IO::Handle;
sub miniget($$$$){
        my($HostName, $PortNumber, $Desired, $agent)  = @_;
        $PortNumber ||= 80;
        # print STDERR ~~localtime,"Trying to connect to $HostName $PortNumber to retrieve $Desired\n";
        my $iaddr       = inet_aton($HostName)  || die "Cannot find host named $HostName";
        my $paddr       = sockaddr_in($PortNumber,$iaddr);
        my $proto       = getprotobyname('tcp');
                                                        
        socket(SOCK, PF_INET, SOCK_STREAM, $proto)  || die "socket: $!";
        connect(SOCK, $paddr)    || die "connect: $!";
        SOCK->autoflush(1);

        print SOCK
                "GET $Desired HTTP/1.1$CRLF",
                # Do we need a Host: header with an "AbsoluteURI?"
                # not needed: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.2
                # but this is trumped by an Apache error message invoking RFC2068 sections 9 and 14.23
                "Host: $HostName$CRLF",
                "User-Agent: $agent$CRLF",
                "Connection: close$CRLF",
                $CRLF;

        join('',<SOCK>);

};



sub Authenticate{

        my %Param = (agent => 'AISclient', @_);
        my %Result;
        my $AISXML;

        # print STDERR "cookie string is $ENV{HTTP_COOKIE}\n";

        my ($Cookie) = ($ENV{HTTP_COOKIE} =~  /AIS_Session=(\w+)/);
        tie my %Session, $Param{tieargs}->[0],
        $Param{tieargs}->[1],$Param{tieargs}->[2],$Param{tieargs}->[3],
        $Param{tieargs}->[4],$Param{tieargs}->[5],$Param{tieargs}->[6],
        $Param{tieargs}->[7],$Param{tieargs}->[8],$Param{tieargs}->[9]
                or croak "failed to tie @{$Param{tieargs}}";

        if ($Cookie and ! $Session{$Cookie}){
                $Cookie = '';

        };

        my $OTUkey;
        my $SessionKey;
        if ($ENV{QUERY_STRING} =~ /AIS_OTUkey=(\w+)/){
           $OTUkey = $1;

           my ($method, $host, $port, $path) =
             ($Param{aissri} =~ m#^(\w+)://([^:/]+):?(\d*)(.+)$#)
              or die "Could not get meth,hos,por,pat from <$Param{aissri}>";
           unless ($method eq 'http'){
                croak "aissri parameter must begin 'http://' at this time";
           };

           # print STDERR "about to miniget for: ${CRLF}GET $Param{aissri}query?$OTUkey$CRLF$CRLF";
           # my $Response = `lynx -source $Param{aissri}query?$OTUkey$CRLF$CRLF`
           my $Response = miniget $host, $port,
           "$Param{aissri}query?$OTUkey", $Param{agent};

           $SessionKey = join('',time,(map {("A".."Z")[rand 26]}(0..19)));
           print "Set-Cookie: AIS_Session=$SessionKey;$CRLF";
           ($AISXML) =
                $Response =~ m#<aisresponse>(.+)#si
                   or die "no <aisresponse> element from $Param{aissri}query?$OTUkey\n";
           $Session{$SessionKey} = $AISXML;

        }elsif (!$Cookie){
                print "Location: $Param{aissri}present?http://$ENV{SERVER_NAME}$ENV{REQUEST_URI}?AIS_OTUkey=\n\n";
                exit;
        }else{ # We have a cookie
                $AISXML = $Session{$Cookie};
                delete  $Session{$Cookie} if $ENV{QUERY_STRING} eq 'AIS_LOGOUT';
        };

        foreach (qw{
                        identity
                        error
                        aissri
                        user_remote_addr
                       },
                    @{$Param{XML}}
        ){
                # print STDERR "Looking for $_ in XML\n";
                $AISXML =~ m#<$_>(.+)#si or next;
                $Result{$_} = $1;
                # print STDERR "Found $Result{$_}\n";
        };

        if ( defined($Param{timeout})){
                my $TO = $Param{timeout};
                delete @Session{ grep { time - $_ > $TO } keys %Session };

        };

        #Suppress caching NULL and ERROR
        if( $Result{identity} eq 'NULL' or $Result{identity} eq 'ERROR'){
                print "Set-Cookie: AIS_Session=$CRLF";
                $SessionKey and delete $Session{$SessionKey} ;
        };
        # print STDERR "About to return session object\n";
        # print STDERR "@{[%Result]}\n";
        return \%Result;
};


# Preloaded methods go here.

1;
__END__

=head1 NAME

CGI::AIS::Session - Perl extension to manage CGI user sessions with external identity authentication via AIS

=head1 SYNOPSIS
  use DirDB;    # or any other concurrent-access-safe
                # persistent hash abstraction
  use CGI::AIS::Session;
  my $Session = Authenticate(
             aissri <= 'http://www.pay2send.com/cgi/ais/',
             tieargs <= ['DirDB', './data/Sessions'],
             XML <= ['name','age','region','gender'],
             agent <= 'Bollow',      # this is the password for the AIS service, if needed
             ( $$ % 100 ? () : (timeout <= 4 * 3600)) # four hours
  );
  if($$Session{identity} eq 'NULL'){
        print "Location: http://www.pay2send.com/cgi/ais/login\n\n"
        exit;
  }elsif($Session->{identity} eq 'ERROR'){
        print "Content-type: text/plain\n\n";
        print "There was an error with the authentication layer",
              " of this web service: $Session->{error}\n\n",
              "please contact $ENV{SERVER_ADMIN} to report this.";
        exit;
  }
  tie my %UserData, 'DirDB', "./data/$$Session{identity}";
 

=head1 DESCRIPTION

Creates and maintains a read-only session abstraction based on data in
a central AIS server.

The session data provided by AIS is read-only.  A second
database keyed on the identity provided by AIS should be
used to store persistent local information such as shopping cart
contents. This may be repaired in future releases, so the 
session object will be more similar to the session objects
used with the Apache::Session modules, but for now, all the
data in the object returned by C<Authenticate> comes from the
central AIS server.

On the first use, the user is redirected to the AIS server
according to the AIS protocol. Then the identity, if any,
is cached
under a session key in the session database as tied to by
the 'tieargs' parameter.

This module will create a http cookie named AIS_Session.

Authenticate will croak on aissri methods other than
http in this version.

Additional expected XML fields can be listed in an XML parameter.

If a 'timeout' paramter is provided,  Sessions older than
the timeout get deleted from the tied sessions hash.

'ERROR' and 'NULL' identities are not cached.

Internally, the possible states of this system are:

no cookie, no OTU
OTU
cookie

Only the last one results in returning a session object. The
other two cause redirection.

if a query string of AIS_LOGOUT is postpended to any url in the
domain protected by this module, the session will be deleted before
it times out.

=head1 EXPORTS

the Authenticate routine is exported.

=head1 AUTHOR

David Nicol, davidnico@cpan.org

=head1 SEE ALSO

http://www.pay2send.com/ais/ais.html

The Apache::Session family of modules on CPAN


=cut








#!/usr/bin/perl -Iguarded



use DirDB;   # or any other concurrent-access-safe
             # persistence abstraction
use CGI::AIS::Session;
my $Session = Authenticate(
   aissri => 'http://www.pay2send.com/cgi/ais/',
   agent => "guarding: $ENV{SERVER_NAME}$ENV{SCRIPT_NAME}",
   tieargs => ['DirDB', './guarded/Sessions'],
  ( $$ % 100 ? () : (timeout => 4 * 3600)) # four hours
);

if($$Session{identity} eq 'NULL'){     
   print "Location: http://www.pay2send.com/cgi/ais/add\n\n";
   exit;
 }elsif($Session->{identity} eq 'ERROR'){
   print <<EOF;
Content-type: text/plain

There was an error with the authentication layer
of this web service: $Session->{error}

please contact $ENV{SERVER_ADMIN} to report this.
EOF

   exit;
}


print <<EOF;
Content-type: text/plain

we have session $Session

@{[keys %$Session]}
@{[values %$Session]}

Path-info is $ENV{PATH_INFO}

EOF

if (-e "guarded/text/$ENV{PATH_INFO}"){
        open TEXT,"<guarded/text/$ENV{PATH_INFO}";
        while (<TEXT>){
                print
        };
	
	print "\n\nTo log out, access http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}?AIS_LOGOUT\n";
}else{

        print "no such document found.\n";

}

__END__





The example programs here use a trivial flat file database with key names as file names. It is available on CPAN as "DirDB." 
The way multiple AIS embedding would work is like this:

 instead of offering a log-in page that refers to a single identity authority, the "splash page" of the guarded area has multiple images embedded in it, each a hyperlink to its respective login page.

 When the user tries to display the image, an AIS client handshake occurs, and a "logged in" or "not logged in" image will be displayed, either directly by a program, or through redirection to a static image. "Click here to log in" makes more sense than "not logged in." This AIS client feature is under development.

We have set you up a session based on AIS response from this server

You are not logged in at this server

We already have a session for you so we didn't bother this server

Something went wrong when we tried to check this AIS


Which server utility uses which kinds of persistent data?
server program SSO keys Sessions OTU keys explanation
add write creates a single-sign-on mapping
login read write creates a mapping from a session cookie to a SSO mapping
not an identity, so "delete" will end all current sessions
present read write creates a one-time-use mapping
query write looks up an identity by OTU key, which it deletes
logout write deletes a session key
delete write deletes a SSO key

The AIS handshake as a time-state chart
step user AIS server AIS client
user requests a protected resource
but has no session cookie.
  []--- ------------- -->
The AIS client redirects the user
to the AIS server.
   <-- ------------- ---[]
  []--- --->  
The AIS server redirects the user back
to the AIS client, along with a one-time key
   <-- ---[]  
  []--- ------------- -->
The AIS client connects directly with
the AIS server to exchange one-time key for identity
    <-- ---[]
    []--- -->
The AIS client serves a session cookie and
the requested page, or sends the user to a login page
   <-- ------------- ---[]

In early drafts (Yes, something this simple does goes through more than one draft) of this protocol, the one-time-use keys were generated by the AIS client. This approach would be vulnerable to key collision problems, and also assymetric network visibility problems, and that is why I think that the additional step is justified: the client can be simpler. 


webservice subscriber identification method Providers of AIS Services that wish to limit access to their databases, for privacy or subscription-based business model, are encouraged to embed a password in the user-agent header provided in the QUERY request, which is supported by the miniget routine in the example module as well as by LWP. Limiting access to queries based on REMOTE_ADDR would work as well, but it is kind of hacky to rely on a transport-layer implementation detail which is subject to change to implement a higher-level feature.
Each installation of an AIS server should provide some information about its intended purpose and privacy and subscription policies and so on at a standard place, ${aissri}about. One way to provide this is to have an "about" program that prints out your about page. Another is to have an "about" program that prints a redirection header that sends the user to the static about page, such as

#!/bin/sh
echo Location: /ais/about.html
echo


If you would like to help develop this project, please join the discussion list at http://savannah.gnu.org/projects/tjais/