2
\$\begingroup\$

During each HTTP request incoming from my Angular front-end I send encrypted pair of access & refresh token. On the back-end ASP.NET web APIs are used.

The way I decrypt them currently is by creating 2 custom middlewares in one of the APIs (one after another):

  • The first one decrypt the access token, modifies the http request header with the decrypted information and passes it to the next middleware.
  • The second one checks if the (decrypted) access token is expired and depending on this - issues new access token, using the (decrypted) refresh token and again modifies the HTTP header with the new access token.

Here is my implementation.

1st middleware:

 public async Task InvokeAsync(HttpContext context)
        {
            string decryptedAccessToken = await tokenService.DecryptToken(context);

            if (string.IsNullOrEmpty(decryptedAccessToken))
            {
                await _next(context);
                return;
            }

            context.Request.Headers.Remove("Authorization");
            context.Request.Headers.Add("Authorization", "Bearer " + decryptedAccessToken);

            await _next(context);
        }

2nd middleware:

public async Task InvokeAsync(HttpContext context)
        {
            var tokenExpirationString = await tokenService.ExtractTokenClaimValue(context, ExpirationClaimName);
            var tokenExpirationValue = 0l;

            if (string.IsNullOrWhiteSpace(tokenExpirationString) || !long.TryParse(tokenExpirationString, out tokenExpirationValue))
            {
                await _next(context);
                return;
            }

            var tokenExpirationTime = DateTimeOffset.FromUnixTimeSeconds(tokenExpirationValue).LocalDateTime;
            var currentTime = DateTime.Now;
            
            if (tokenExpirationTime <= currentTime)
            {
                await tokenService.GetNewAccessToken(context);
                await _next(context);
                return;
            }

            await _next(context);
        }

I issue my tokens using Identity Server.

My question is - is this methodology of mine according to good practices or not?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

I will not comment on the code. I just want to point out that this way you nullify some security that separate tokens provide. Specifically, it doesn't matter that you encrypted the pair, if I steal the encrypted pair I get long term access to your api the same way as if you were using just one long lived token. You are supposed to only send access token to your api. As long as it can be verified by the server, trust it. If it expires return 401 to the client. Then the client is responsible for refreshing the access token by sending refresh token to identity server. In other words, your resource server should never see your refresh token. Refresh token is for the client and identity server.

Further, in your implementation, once the original access token expires, the newly generated one is never returned to the client and so the client is doomed to keep sending expired access token and your server is doomed to refresh that token over and over until the refresh token expires as well.

\$\endgroup\$
6
  • \$\begingroup\$ Thank you. I am still new to web security so every feedback is good for me. Well, I talked to different people and some say that the API should refresh the token, some say it is wrong. I am confused with so many different opinions. The problem with the front end issuing new access token is that I do not know how to encrypt the data, since all the code is visible through the browser. If I solve the encryption problem, I will re-implement it. \$\endgroup\$ Commented Nov 11, 2022 at 22:17
  • \$\begingroup\$ Also, about the stealing part - can you explain how to protect against such actions. Isn't the CORS policy effective? For example, if you steal my http request and re-send it, you should be blocked by CORS, shouldn't you? \$\endgroup\$ Commented Nov 11, 2022 at 22:31
  • \$\begingroup\$ Yea it's difficult, most people don't understand it themselves. You don't need to encrypt access token. If you think it contains sensitive information, just don't put them there. \$\endgroup\$ Commented Nov 12, 2022 at 7:05
  • \$\begingroup\$ CORS only protects you if youre using main stream browser, but you can always send a request with curl, postman, etc and make it ignore any CORS. \$\endgroup\$ Commented Nov 12, 2022 at 7:07
  • \$\begingroup\$ If access token is stolen, you mitigate the consequences by issuing access token only for short period of time ie 5mins, to limit the time the attacker can misuse the token. You also issue access tokens with limited scope so that stolen access token reveals access to only a small part of the system. \$\endgroup\$ Commented Nov 12, 2022 at 7:16
1
\$\begingroup\$

I would suggest to merge the two middlewares into one to make the data flow more explicit.

According to my understanding you want to achieve something like this:

  • Decrypt the token
    • If it can't be decrypted and parsed properly then stop further execution
  • Validate token's freshness
    • If is is expired request a new one
  • Add the token to the headers
  • Call the next middleware

In code it could look something like this:

public async Task InvokeAsync(HttpContext context)
{
    var accessToken = await tokenService.DecryptToken(context);
    if (string.IsNullOrEmpty(accessToken))
    {
        //Throw exception to break the execution
    }

    var tokenExpirationRaw = await tokenService.ExtractTokenClaimValue(context, ExpirationClaimName);
    if (string.IsNullOrWhiteSpace(tokenExpirationRaw) || !long.TryParse(tokenExpirationRaw, out var tokenExpiration))
    {
        //Throw exception to break the execution
    }

    var tokenExpirationTime = DateTimeOffset.FromUnixTimeSeconds(tokenExpiration);
    if (tokenExpirationTime <= DateTimeOffset.Now)
    {
        accessToken = await tokenService.GetNewAccessToken(context);
    }

    context.Request.Headers.Remove(HeaderNames.Authorization);
    context.Request.Headers.Add(HeaderNames.Authorization, $"Bearer {accessToken}");

    await _next(context);
}

Please bear in mind that your server's system clock might differ from the token issuer server's clock. Even if there is a time synchronization protocol in place there might be some time skew.

\$\endgroup\$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.