Why you may want to read this article
In the previous article about basics, we considered some auth terminology, and explored all diversity of existing authentication and authorization protocols and schemes.
In this part, I’ll explain and implement Basic
and Digest
Auth schemes.
Basic Scheme
It is the simplest auth scheme:
- get username and password
- concatenate them with “:”
- encode with Base64
- send a request with an Authorization header
- The
Server
validates username and password internally
Using this scheme we cannot decouple Auth
from the Resource server
. So for each request, the Server
should get all info from the Authorization header, authenticate the User
and then authorize him as well. It means that API
should know who the User
is (store him or know where to get it). To be able to decouple we will consider JWT tokens later on.
Note: encoding to base64 is needed for encoding special characters, to ensure the string contains only ASCII characters it will not
give you any security layer.
Since the user passes his credentials in a raw state - it is required to use one more security layer on top of the HTTP application layer - TLS.
HandleAuthenticateAsync
first does the authentication: it extracts username and password from the header (decoding base64) and ensures this user does exist (in our case it is a simple if statement, but in the real case, it should hash the pass and check it along with username). It does it for every request because we used .UseAuthorization() .UseAuthentication()
middleware registration methods.
But this is the Authentication
part of it, we only checked that the user is who he claims to be.
After HandleAuthenticateAsync
did the authentication part we will have User.Identity
in each endpoint, and based on this identity we can do Authorization
- to verify whether this particular user is allowed to access this endpoint or not.
However, Basic Auth
is so simple it brings a few problems:
- It is not secured, you need to send your unencrypted unhashed password through the network
- It doesn’t have any defined way of logging out and logging in the user.
- No built-in protocol for giving different permissions for different endpoints, roles, etc. Meaning, that
Resource Server
will not have claims (role, static, etc) that it can validate in the token. This forces eachResource Server
to know all user permissions and store them inside. - The
API
should know the user (store it)
Digest Scheme
First of all, I should mention that this sample I rewritten from this repo, so you can see the original version
It is a little bit complicated auth
flow compared to Basic Auth
. It doesn’t pass a password through the network, instead of this we are creating and storing some hashes.
Using this scheme we cannot decouple Auth
from Resource server
. So for each request, the Server
should get all info from the Authorization header, authenticate the User
and then authorize him as well. It means that API
should know who the User
is (store him or know where to get it). To be able to decouple we will consider JWT tokens later on.
This flow requires you to call the endpoint 2 times if you are not authenticated. First, you should receive 401 with the necessary headers and the next call is done with a generated digest token (on Client
side) based on those headers.
Prior to explaining the flow, we should introduce some terminology:
Username
- just login provided by the userPassword
- just the password provided by the userRealm
- area which you can access with a particular digest ticket, something similar to scope inOAuth
. You might return the samerealm
value for some particular set of endpoints and you will have access to those endpoints with the sameauth
ticket.Nonce (number once)
-Server
-generated string which should be unique for all 401 responses.CNonce (client nonce)
-Client
-generated string which should be sent and verified by theClient
.The Server
doesn’t care about it and includes it as a response so that theClient
can confirm that it is the rightServer
. It is used for plain-text attack mitigation (when an attacker has a plaintext and encrypted version, based on that he could reveal secrets, code blocks, etc).Nc (nonce count)
- the number of requests (including the current one) that theClient
has sent with thenonce
value in it.Opaque
-Server
-generated string which should be sent unchanged by theClient
.Qop (quality of protection)
- defines whether the hash of entity-body is added to hashes or not. It brings additional integrity. It might contain either “auth
” or “auth-int
” values.Response
- in terms ofDigest auth
it is a kind of signature, just a hash, based on values that will be sent to theServer
to be verified.
For this auth, we may use different hash algorithms, but for simplicity, we will use MD5.
The flow is the following:
1.The Client
performs a request to Resource Server
without any Auth
.
2.The Client
collects realm
, qop
, nonce
, and opaque
from the response header.
3.The Client
generates a request auth token:
First Hash (A1) = MD5 (username
:realm
:password
)
Note: we can use the MD5-sess algorithm, which essentially means to use the same algorithm, but in this format:
First Hash (A1) = MD5 (MD5 (username
:realm
:password
):nonce
:cnonce
)
Why would we like to do it? It allows you to not care about the user’s password. On top of that, I think, it allows you to use a different algorithm for hashing user credentials (username:realm:password) for security purposes. And for auth
we can use another algorithm with nonce
and cnonce
values.
Second Hash (A2) = MD5 (httpMethod:requestUrl)
Response = MD5 ({First Hash}:nonce
:nonceCount
:cnonce
:qop
:{Second Hash})
4.The Client
sends the same request including the Authorization header in the following format: Digest username
=”{username}”,
realm
=”{realm }”,
nonce
=”{nonce}”,
uri
=”{uri}”,
qop
={qop},
nc
={nc},
cnonce
=”{cnonce}”,
response
=”{response}”,
opaque
=”{opaque}”
5.The Server
verifies the hash by using values provided by the Client
(authenticate and authorize). Then it either rejects with a 4XX error or serves the response.
There is one interesting question: How is the Server
supposed to generate the first hash without knowing the password since the Client
doesn’t pass it on request?
And there are 2 main solutions:
- we either store plain passwords (VERY VERY BAD APPROACH),
- or we store the hashed value of (
username
:realm
:password
) to be able to get the hash byusername
andrealm
. And this brings another problem. Once you realize you want to change a hash (for example you used firstly MD5 and then some security weaknesses were detected and you want to change to SHA-256) you cannot do much. But actually, this problem arises always when we are dealing with password storing and comparing.
Compared to Basic Auth
this flow fixed a lot of problems:
- There is no plain password passed through the network
- You can configure nonce lifetime to allow access only for a certain period of time. It is possible to log out all users by changing
opaque
values, this will force everybody to re-authenticate. - You can specify different access areas (different controllers, endpoints, etc) via
realm
.
However, it still has some problems:
- No built-in protocol for giving different permissions for different endpoints, roles, etc. Meaning, that
Resource Server
will not have claims (role, static, etc) that it can validate in the token. This forces eachResource Server
to know all user permissions and store them inside. - The
API
should know the user (store it). You can fix it by storing a hash for a particular username and realm, but still, it is some kind of knowledge of the user compared to JWT for example.
Conclusion
Basic Auth
is pretty simple to implement but it has a lot of functionality and security lacks. Digest Auth
is much more complicated but solves some of the problems that Basic Auth
has, but still, it has some lack of functionality that regular applications would need for the Auth process.
So in the next part, we will consider OAuth
and OpenId Connect
protocols that solved those problems.
Please subscribe to my social media to not miss updates.: Instagram, Telegram
I’m talking about life as a Software Engineer at Microsoft.
Besides that, my projects:
Symptoms Diary: https://symptom-diary.com
Pet4Pet: https://pet-4-pet.com