API Integration Test for Login controller using Identity server4 in asp.net core

maxspan

I am having issues with testing Login Controller using IdentityServer4. It throws the following error:

{System.Net.Http.WinHttpException (0x80072EFD): A connection with the server could not be established

I am trying to generate the access Token using ResourceOwnerPassword, for which I have implemented IResourceOwnerPasswordValidator. I get the error in UserAccessToken.cs class when I call the RequestResourcePasswordAsync. I am pretty sure it is because of the handler. Because if I use a handler in my test class and call the TokenClient with that handler I do get access Token but then I cannot test my Login Controller.

LoginController.cs

[HttpPost]
public async Task<IActionResult> Login([FromBody]LoginViewModel user)
{       
    var accessToken = await UserAccessToken.GenerateTokenAsync(user.Username, user.Password);
    var loginToken = JsonConvert.DeserializeObject(accessToken);
    return Ok(loginToken); 
}

UserAccessToken.cs

public async Task<string> GenerateTokenAsync(string username, string password)
        {
            var tokenUrl = "http://localhost:5000/connect/token";
            var tokenClient = new TokenClient(tokenUrl,"ClientId","ClientPassword");
            var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
            if (tokenResponse.IsError)
            {
                throw new AuthenticationFailedException(tokenResponse.Error);
            }
            return tokenResponse.Json.ToString();

        }

TestClass.cs

 [Fact]
 public async Task Login()
 {          
     var client = _identityServer.CreateClient();
     var data = new StringContent(JsonConvert.SerializeObject(new LoginViewModel { Username = "1206", Password = "5m{F?Hk92/Qj}n7Lp6" }), Encoding.UTF8, "application/json");
     var dd = await client.PostAsync("http://localhost:5000/login", data);
     var ss = dd;
 }

IdentityServerSetup.cs //Integration Test Setup

public class IdentityServerSetup
{
    private TestServer _identityServer;
    private const string TokenEndpoint = "http://localhost:5000/connect/token";
    public HttpMessageHandler _handler;

    //IF I use this code I do get a AccessToken
    public async Task<string> GetAccessTokenForUser(string userName, string password, string clientId, string clientSecret, string apiName = "integrapay.api.public")
    {
        var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: _handler);
        var response = await client.RequestResourceOwnerPasswordAsync(userName, password, apiName);
        return response.AccessToken;    
    }
}
GeorgDangl

Well, you have already answered the question yourself: The problem is with the HttpHandler the TokenClient uses. It should use the one provided by the TestServer to successfully communicate with it instead of doing actual requests to localhost.

Right now, UserAccessToken requires a TokenClient. This is a dependency of your class, so you should refactor the code to pass in a TokenClient instead of generating it yourself. This pattern is called Dependency Injection and is ideal for cases like yours, where you might have different requirements in your tests than in your production setup.

You could make the code look like this:

UserAccessToken.cs

public class UserAccessToken
{
    private readonly TokenClient _tokenClient;

    public UserAccessToken(TokenClient tokenClient)
    {
        _tokenClient = tokenClient;
    }

    public async Task<string> GenerateTokenAsync(string username, string password)
    {
        var tokenUrl = "http://localhost:5000/connect/token";
        var tokenResponse = await _tokenClient.RequestResourceOwnerPasswordAsync(username, password, SecurityConfig.PublicApiResourceId);
        if (tokenResponse.IsError)
        {
            throw new AuthenticationFailedException(tokenResponse.Error);
        }
        return tokenResponse.Json.ToString();
    }
}

TestHelpers.cs

public static class TestHelpers
{
    private static TestServer _testServer;
    private static readonly object _initializationLock = new object();

    public static TestServer GetTestServer()
    {
        if (_testServer == null)
        {
            InitializeTestServer();
        }
        return _testServer;
    }

    private static void InitializeTestServer()
    {
        lock (_initializationLock)
        {
            if (_testServer != null)
            {
                return;
            }
            var webHostBuilder = new WebHostBuilder()
                .UseStartup<IntegrationTestsStartup>();
            var testServer = new TestServer(webHostBuilder);
            var initializationTask = InitializeDatabase(testServer);
            initializationTask.ConfigureAwait(false);
            initializationTask.Wait();
            testServer.BaseAddress = new Uri("http://localhost");
            _testServer = testServer;
        }
    }
}

IntegrationTestsStartup.cs

public class IntegrationTestsStartup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<TokenClient>(() =>
        {
            var handler = TestUtilities.GetTestServer().CreateHandler();
            var client = new TokenClient(TokenEndpoint, clientId, clientSecret, innerHttpMessageHandler: handler);
            return client;
        };
        services.AddTransient<UserAccessToken>();
    }
}

LoginController.cs

public class LoginController : Controller
{
    private readonly UserAccessToken _userAccessToken;

    public LoginController(UserAccessToken userAccessToken)
    {
        _userAccessToken = userAccessToken;
    }

    [HttpPost]
    public async Task<IActionResult> Login([FromBody]LoginViewModel user)
    {
        var accessToken = await _userAccessToken .GenerateTokenAsync(user.Username, user.Password);
        var loginToken = JsonConvert.DeserializeObject(accessToken);
        return Ok(loginToken);
    }
}

Here's one of my GitHub projects that makes use of the TestServer class and shows how I'm using it. It's not using IdentityServer4, though.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

ASP.Net Core Identity login status lost after deploy

How to write Integration Test for Identity Server 4 in asp.net mvc core

Register and Login by Phone Number in Asp.net Core Identity

External Login without using identity asp.net core 2.0

User.Identity.Name is empty in Asp.NET Core 2.0 API Controller

Asp.net Core Identity unit test controller actions

Access user identity from controller in ASP.NET Core Web API

call api Test server : .net core API integration tests

Get user profile in ASP.net core API with Identity Server 4 Implicit Flow

API is authorized without the Authorization Headers in Request using Identity Server 4 and .net core Identity

Using Asp.Net Core Identity in MVC, Authorize attribute is rebouncing to login page after succesfull login

How to do an integration test on my Asp.Net core web api endpoint where I upload a file?

Adding Redirection immediately after Login in ASP.Net Core 2.1 using Identity Core

.Net Core Custom Authentication using API Keys with Identity Server 4

Concurrent login with identity server4

Check claims (email) before creating a new cookie, without asp. core identity store using asp net core social login

SAML integration with ASP.NET Core Identity

How to refresh CSRF token on login when using cookie authentication without identity in ASP .NET Core Web API

Adding [Authorize] to controller failing to redirect to Identity Login route. ASP.NET CORE 3.1 MVC

ASP.NET Core MVC Identity login issue

client specific claims identity server4 using asp.net core identity

How to work with Azure AD external login with ASP.NET Idenity in Identity Server4

How do I get the user details straight after login using Identity on ASP.NET Core?

Login after signup in identity server4

ASP.NET Core returns InternalServerError while using Identity server

Simulating IIS App Pool Identity in ASP.Net Core Integration Test

ASP.NET Core : specify controller name for logging in using identity

ASP.NET Core 3.1: User.Identity.Name is empty in API Controller, but claim name is present

ASP.NET Core Identity in a Web API