HTTP Authentication
Basic Authentication
Besides usual web application form-based authentication, developer can use authentication mechanism that is provided by HTTP protocol as described in RFC 2617.
Currently, Fano Framework supports Basic
and Digest
HTTP authentication scheme only.
Handling Basic Authentication with middleware
Fano Framework provides implementation for basic type HTTP authentication with
TBasicAuthMiddleware
middleware with purpose to simplify task to protect access of certain routes using HTTP authentication.
Read Middlewares for more information about working with middlewares.
Constructor of TBasicAuthMiddleware
expects two parameters,
- Instance of
IAuth
interface which is responsible to perform actual authentication, for example, reading database for existence of credential being check. - String of realm name.
TStaticCredentialsAuth
is built-in IAuth
implementation which test credential against array of allowed credentials.
Developer can create their own custom authentication by implementing IAuth
interface and inject it to TBasicAuthMiddleware
constructor.
How Basic Auth middleware works
Everytime a user tries to access route that is protected with TBasicAuthMiddleware
middleware, user Internet browser, initially does not know that this route requires authentication, thus will cause our application to response with HTTP 401 error code and header WWW-Authenticate
set with value of Basic realm="[realm name]"
.
Upon receiving this response, browser will prompt user to provide their credential. The supplied credential are then sent back to our application thorough Authorization
request header with value,
Authorization: Basic ["username:password" base64 encoded string]
For username hello
and password world
, Base64 encoded of string hello:world
is aGVsbG86d29ybGQ=
.
Authorization: Basic aGVsbG86d29ybGQ=
If credential is valid, our middleware continues to execute next middleware and eventually controller. If credential is not valid, our middleware returns same HTTP 401 response.
Register Basic Authentication middleware with container
Fano Framework has TStaticCredentialsBasicAuthMiddlewareFactory
class
which allows you to register TBasicAuthMiddleware
using TStaticCredentialsAuth
into service container.
container.add(
'basicAuthMiddleware',
TStaticCredentialsBasicAuthMiddlewareFactory.create('fano-realm')
.addCredential('hello@fano', 'world')
.addCredential('hi@fano', 'nice')
);
Above code simplifies and does same thing as following code
var authMw : IMiddleware;
allowedCredentials : TCredentials;
...
setlength(allowedCredentials, 2);
allowedCredentials[0].username = 'hello@fano';
allowedCredentials[0].password = 'world';
allowedCredentials[1].username = 'hi@fano';
allowedCredentials[1].password = 'nice';
authMw := TBasicAuthMiddleware.create(
TStaticCredentialsAuth.create(allowedCredentials),
'fano-realm'
);
Where TCredentials
is declared as follows,
type
TCredential = record
username : string;
password : string;
//user-related data
data : pointer;
end;
TCredentials = array of TCredential;
Register dispatcher with middleware support.
You need to use TDispatcher
class which support middlewares.
container.add('appMiddlewares', TMiddlewareListFactory.create());
container.add(
GuidToString(IDispatcher),
TDispatcherFactory.create(
container['appMiddlewares'] as IMiddlewareLinkList,
routeMatcher,
TRequestResponseFactory.create()
)
);
Attach Basic Auth middleware to application routes
Attach basic auth middleware instance to application routes, for example
router.get('/', container['homeController'] as IRequestHandler)
.add(container['basicAuthMiddleware'] as IMiddleware);
Handling Digest Authentication with middleware
Fano Framework provides implementation for digest type HTTP authentication with
TDigestAuthMiddleware
middleware with purpose to simplify task to protect access of certain routes using Digest HTTP authentication scheme.
Constructor of TDigestAuthMiddleware
expects three parameters,
- Instance of
IRandom
interface which is responsible to generate random value. - Instance of
IAuth
interface which is responsible to perform actual authentication, for example, reading database for existence of credential being check. - String of realm name.
TDigestStaticCredentialsAuth
is built-in IAuth
implementation which test credential against array of allowed credentials.
Developer can create their own custom authentication by implementing IAuth
interface and inject it to TDigestAuthMiddleware
constructor.
Register Digest Authentication middleware with container
Fano Framework has TStaticCredentialsDigestAuthMiddlewareFactory
class
which allows you to register TDigestAuthMiddleware
using TDigestStaticCredentialsAuth
into service container.
container.add(
'digestAuthMiddleware',
TStaticCredentialsDigestAuthMiddlewareFactory.create('fano-realm')
.addCredential('hello@fano', 'world')
.addCredential('hi@fano', 'nice')
);
Attach Digest Auth middleware to application routes
Attach digest auth middleware instance to application routes, for example
router.get('/', container['homeController'] as IRequestHandler)
.add(container['digestAuthMiddleware'] as IMiddleware);
Handling Bearer Authentication with middleware
Fano Framework provides implementation for bearer type HTTP authentication with
TBearerAuthMiddleware
middleware with purpose to simplify task to protect access of certain routes using Bearer HTTP authentication scheme.
Constructor of TBearerAuthMiddleware
expects three parameters,
- Instance of
ITokenVerifier
interface which is responsible to perform actual token verification. - String of realm name.
- String of credential key name where authenticated credential can be queried from request.
Currently, Fano Framework supports JSON Web Token (JWT) verification only, via TJwtTokenVerifier
class which implements ITokenVerifier
interface.
After token is verified, credential info found in token is stored in request which later can be queried in controller using name you defined in credential key name.
Register Bearer Authentication middleware with container
Fano Framework has TBearerAuthMiddlewareFactory
class
which allows you to register TBearerAuthMiddleware
into service container.
container.add(
'bearerAuth',
TBearerAuthMiddlewareFactory.create()
.realm('fano-realm')
.verifier(container['tokenVerifier'] as ITokenVerifier)
.credentialKey('fano_cred')
);
Attach Bearer Auth middleware to application routes
Attach bearer auth middleware instance to application routes, for example
router.get('/', container['homeController'] as IRequestHandler)
.add(container['bearerAuth'] as IMiddleware);
In code above, for each GET request to URL /
, middleware will check token existence and verify
if it is found. If token is not found or not verified, it returns HTTP 401 response
with WWW-Authenticate: Bearer realm="[realm name]"
header,
where [realm name]
is realm that you set above.
When token is verified, credential found from token can be read from request in controller with
credential key you set before, i.e, fano_cred
.
function THomeController.handleRequest(
const request : IRequest;
const response : IResponse;
const args : IRouteArgsReader
) : IResponse;
var username : string;
begin
username := request.getParam('fano_cred');
response.body().write('Hello ' + username);
result := response;
end;
Security consideration
For Basic HTTP authentication scheme, username and password is transmitted as Base64-encoded string. It is prone to man in middle attack and it is must be used in conjunction with SSL/TLS.
Digest HTTP authentication scheme is more computation expensive but can be used with or without SSL/TLS because password and other data is sent to server as MD5 hashed value. However, because MD5 hash is not cryptographically strong, you need to be cautious when use it without SSL/TLS.
For Bearer HTTP authentication, token is credential. It must be used in conjunction with SSL/TLS to avoid man in middle attack. Token may or may not be encrypted. If you use unencrypted JWT token, do not store sensitive data in token.
To address when token is leaked to third party, You can combine short-lived token with short expiry time (access token) and login-lived token (refresh token). When access token is expired, client must get new access token from authentication server using refresh token. If refresh token is expired then client session is ended. To get new refresh token and access token, client must login again using username password.
Missing Authorization header
If your application is running behind reverse proxy, for example, with mod_proxy_scgi
module, Apache does not pass Authorization
header to application because of security concern. This will cause all middlewares above return HTTP 401 as they can not find this header.
In case, you have trusted network between Apache and your application, simple solution is to transform Authorization
header into HTTP_AUTHORIZATION
environment variable using mod_rewrite
module. You can add it in application virtual host configuration file.
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
</IfModule>