-
-
Notifications
You must be signed in to change notification settings - Fork 1
End to end authentication flow
This section describes each step of the authentication flow in detail. All code examples are referencing the original PHP implementation of the client library.
The consumer application is the software project that requires authentication to be provided by Authwave (it will consume the authentication that the Authwave Provider provides). This could be any server-side web application such as a blog, e-commerce website, email service, etc.
For a consumer application to be able to communicate with Authwave, it needs to be configured within the Authwave host (typically www.authwave.com, unless using a self-hosted provider).
From the Authwave host configuration, you will create an Application
with at least one Deployment
. A Deployment is required for each host that will be serve a consumer application. Your application in production, any staging varieties and a local development copy are all separate Deployments of the same Application.
Each deployment is configured to have its own:
- Client key - a randomly generated set of alphanumeric characters, unique to the application.
- Client host - the hostname where the consumer application is hosted from.
- Provider host - the hostname where the Authwave provider is hosted.
Typically the login host will be the client host prefixed with account.
. For example, a website with client host of www.example.com
could have a login host of account.example.com
, but this can be configured to however the developer chooses.
Authwave then needs to be installed on the server. For PHP applications this is done by installing using Composer. In the future, other languages are planned to be officially supported.
When the consumer application is ready to authenticate a user, a new instance of the Authwave Client must be instantiated:
$auth = new \Authwave\Authenticator("your-client-key", $currentUriPath, "account.example.com");
The first parameter is the Application Deployment's client key. The second parameter is the current URI's path. In PHP, this can usually be obtained with $_SERVER["REQUEST_URI"]
. The third parameter is the hostname of where the provider is hosted.
By default, PHP has global access to session variables and response headers. In languages that do not have global access to these, or in PHP frameworks that encapsulate that functionality, it is required to pass more parameters to the Authenticator
: a fourth parameter of a session handler and a fifth parameter of a redirect handler.
Constructing the Authenticator
object firstly sets all properties on the object, then calls the completeAuth()
function which will handle any authentication flow that is currently in progress. If there is no authentication flow in progress, completeAuth()
will return early. More on this later.
Once the Authenticator
object is constructed, we can call $auth->isLoggedIn()
to obtain a boolean indicating whether there is currently an authenticated user on the session.
Behind the scenes, isLoggedIn
checks for the presence of UserData
stored in the session, returning true if it exists and false if it doesn't. UserData
is a data transfer object that represents the data sent back from the Authwave provider (email, name, etc.).
Within the consumer application, the action that triggers authentication depends on what application is being developed, but it usually consists of submitting a form or following a link.
Once the consumer application reacts to the user action, we can simply call the login()
function of the Authenticator
object to begin the Authentication flow. This is the only action the consumer application needs to make, as the rest is handled on the Authwave server.
if($userRequestedLogin) {
$auth->login();
}
Within the login function, the following three actions take place:
- Generate a new
Token
. The login function can receive a predefinedToken
object as an optional parameter, for testing purposes. AToken
is the encryption/decryption mechanism used to transfer authentication details. More on this later. - A new
SessionData
object is created to store the newly-generatedToken
. TheSessionData
object is then stored onto the session. - Finally, the user agent is redirected to the Authwave provider. The redirect URI contains encryption details in the query string.
Under normal circumstances, a new Token should be created each time login occurs. A Token
is constructed with the client key, and generates two InitVector
objects - one private and one public. It's possible to pass pre-constructed InitVector
objects in the Token
's constructor for testing purposes, but under normal circumstances these will be generated automatically.
A token has a function generateRequestCipher()
, which takes an arbitrary string message as an optional parameter. A request cipher is a string containing the private IV and optional message, encrypted with the client key using the AES128 cipher method. If a message is supplied, it will be concatenated onto the private IV using the pipe character (|
). The initialisation vector used in the AES128 cipher generation is the public IV.
An InitVector
is a representation of cryptographically secure pseudo-random bytes, represented as a hex string. There must be no way to predetermine the bytes represented by an InitVector
. At the time of writing this, InitVector
s are comprised of 16 random bytes.
During the authentication process, we generate two InitVector
objects. One private and one public. The private IV is stored in the session on the consumer application's server. The public IV will be sent to the provider application's server in the Login URI.
SessionData
is a simple Data Transfer Object with no functionality of its own. It simply constructs with Token
and an optional ResponseData
parameters, and provides getter functions for these two properties. The purpose of the SessionData
object is to provide an easy interface for passing around the data required by the client library in session storage.
A URI
is a common object in any web project. In PHP, any URI
object used will be an instance of the Psr\Http\URI
interface (PSR-7).
Within the client library, an AbstractProviderUri
class is used to define the common functionality of normalising URIs (ensuring the correct scheme is present, that there is a hostname present and that only non-standard ports are present), and building the query string parts. This allows there to be simple classes representing the different URIs in the system, including the LoginUri
, LogoutUri
, ProfileUri
and AdminUri
.
The LoginUri
is used to send the user agent to the Authwave provider with the cipher as the first step of the authentication process. It is constructed with the current Token
, the current path and the hostname of the provider as its parameters. LogoutUri
sends the user agent to the Authwave provider and instructs the provider to perform a logout action (by passing the action in the "message" within the cipher). ProfileUri
and AdminUri
are used to send the user agent to the corresponding configuration areas of the provider.
The AbstractProviderUri
class provides the buildQuery()
function, which returns a query string containing the following key value pairs:
-
c
: The request cipher (base64 encoded). -
i
: The public initialisation vector (represented in hex). -
p
: The current path (represented in hex). -
d
: The deployment ID (first half of the key).
When the client library's login()
function is called, a LoginUri
will be created to redirect the user agent to the Authwave provider's login screen. The provider will have all information required to return the user agent back to the consumer application when authentication succeeds.
The completeAuth()
function is private and therefore can't be called directly from the consumer application. Instead, it is always called whenever the Authenticator
object is constructed. This ensures that the state of the consumer application will always be either logged in or logged out, handled automatically for the developer.
The first job of completeAuth
is to check if there is any data coming back from the provider. If there is none, return early. Data is sent back from the provider in the AUTHWAVE_RESPONSE_DATA
query string parameter. If this is present, the completeAuth
will continue processing it.
Response data is only ever sent after a successful authentication. However, somebody may try to manipulate the data, or the provider may not be configured correctly, in which case the function will throw an exception as it will not be able to decrypt the response cipher.
Important security note: it is only ever possible to decrypt the response data on the same session that created the request. This is because the secret IV is only ever stored in the session of the consumer application's server. For load balanced systems, the SessionHandler interface must accommodate this.
Once the query string parameter is obtained by completeAuth()
, the Token
is retrieved from the session. That same Token
object is then used to decrypt the response data, which has been encrypted via using AES128 with the application client key and this time using the private IV, rather than the public one.
The decrypted data is a JSON string that has object properties for all user data that was provided by the provider. An example string: {"uuid": "aabbccddeeff1234", "email": "[email protected]"}
. A data transfer object of type UserData
is constructed, assigned with a Token
to a new SessionData
object and stored in the session.
Once the UserData is created and stored in the session, the user agent can be redirected to the URI without the querystring. This whole process will happen instantly, and the user should not be aware of the query string at all.
After the redirect occurs, $auth->isLoggedIn()
will return true
until the session is cleared or the user logs out.