3

I am currently using an async Task method to implement the IAuthenticationFilter interface. Upon successful login, I'll try to access an API that has this attribute and it will work fine. However once I go back and access the API again the exception will be thrown.

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
    var token = context.Request.Headers.Authorization.Parameter;
    var principal = await AuthenticateToken(token)
    // Other code here ... 
}

protected Task<IPrincipal> AuthenticateToken(string token)
{
    var secretKey = _authenticationBusiness.GetSecretKey(); // error triggers here.

    if (principal == null)
        context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
    else
        context.Principal = principal;
}

//AuthenticationBusiness.cs
public string GetSecretKey()
{
    using (_unitOfWork)
    {
        var token = _unitOfWork.Tokens.GetToken();

        return token.SecretKey ?? string.Empty;
    }
}

//Dependency Injection using Unity
    container.RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager());
    container.RegisterType<IContext, Context>(new HierarchicalLifetimeManager());

//UnitOfWork.cs
private readonly IContext _context;

public UnitOfWork(IContext context, IJWTRepository tokens)
{
    _context = context;
    Tokens = tokens;
}

public IJWTRepository Tokens { get; private set; }

public void Dispose()
{
    _context.Dispose();
}


//Context.cs
public class Context : DbContext, IContext
{
    public new void SaveChanges()
    {
        base.SaveChanges();
    }

    public new void Dispose()
    {
        base.Dispose();
    }
}

//JWTRepository.cs
public class JWTRepository : Repository<JsonWebToken>, IJWTRepository
{
    public JWTRepository(Context context) : base(context) { }

    public JsonWebToken GetToken()
    {
        return Context.Tokens
            .OrderBy(jwt => jwt.Id)
            .Take(1)
            .SingleOrDefault();
    }

    private Context Context => _context as Context;
}

If I try to remove this attribute and access the API multiple times nothing wrong happens so I am assuming that this has something to do with the fact that the attribute has asynchronous methods?

Josh Monreal
  • 754
  • 1
  • 9
  • 29
  • 3
    It's because you are disposing it. Your `GetSecretKey()` is wrapped in `using()` which will dispose `_unitOfWork` – JohanP Aug 01 '18 at 05:29
  • 1
    using() essentially will dispose the item once it's out of scope. You also may want to pass your CancellationToken into your AuthenticateToken() method if you're doing anything else that is async (which I assume will be the case given you return a Task etc). – Glenn Watson Aug 01 '18 at 05:38
  • @JohanP: If the disposing is the problem, how come it works fine when I remove this attribute? Meaning if I try to access the API multiple times without IAuthenticationFilter everything works fine. – Josh Monreal Aug 01 '18 at 05:41
  • @GlennWatson: There is no async method inside the AuthenticateToken method.I updated the method. – Josh Monreal Aug 01 '18 at 05:43
  • 1
    When you access it multiple times you will getting a new context, your DI will inject a new one for each request, when you have the filter, the context is still the same one from the request, you are accessing unitOfWork multiple times in one request. – JohanP Aug 01 '18 at 05:43

2 Answers2

2

When the lifetime of an IDisposable object is limited to a single method, you should declare and instantiate it in the using statement. The using statement calls the Dispose method on the object in the correct way, and (when you use it as shown earlier) it also causes the object itself to go out of scope as soon as Dispose is called. Within the using block, the object is read-only and cannot be modified or reassigned.

using Statement (C# Reference)

In your code, the problem is that you are wrapping GetSecretkey() into using() which will dispose _unitOfWork and when you will try to access it again it will show an error.

Hope this code works for you.

//AuthenticationBusiness.cs

public string GetSecretKey()
{
    var token = _unitOfWork.Tokens.GetToken();

    return token.SecretKey ?? string.Empty;
}
habib
  • 2,366
  • 5
  • 25
  • 41
  • It works like a charm. However, how will I dispose _unitOfWork properly in the event that an exception is thrown, say at `_unitOfWork.Tokens.GetToken();`? – Josh Monreal Aug 01 '18 at 05:57
  • 2
    @JoshMonreal Your DI will dispose it for you, the rule of thumb is the thing that creates an object is responsible for disposing it. – JohanP Aug 01 '18 at 06:13
0

The problem in in your function AuthenticateToken. When using async-await, make sure you await every Task before returning, if the Task Disposes items. See what is the purpose of return await. The first answer focuses on disposable object

I assume you omitted parts of method AuthenticateToken, because I don't say the return value.

Solution: declare the method async, and await for the Task before returning

async Task<IPrincipal> AuthenticateToken(string token)
{
    var secretKey = _authenticationBusiness.GetSecretKey(); 
    ...
    // Somewhere there is a Task involved, 
    Task<IPrincipal> myTask = ...

    // instead of return myTask:
    return await myTask;
}
Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116