IdentityServer4 with Resource API in .NET 4.5 (OWIN)

Viewed 959

I've read through numerous samples, as well as the IdentityServer 4 documentation, but I still seem to be missing something.

Basically, I have IdentityServer4 working to the point that it is proving me an AccessToken and a RefreshToken. I then try to use that AccessToken and sent an HTTP request to my WebAPI2 (.NET 4.5, OWIN), which uses IdentityServer3.AccessTokenValidation which should be compatible based on samples/tests at https://github.com/IdentityServer/CrossVersionIntegrationTests/

The WebAPI2 is giving me at HTTP 400 when I try to access a resource which required Authorization, and I am truly clueless as to why it happens.

Here is the code:

QuickstartIdentityServer Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    var connectionString = @"server=(localdb)\mssqllocaldb;database=IdentityServer4.Quickstart.EntityFramework;trusted_connection=yes";
    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;

    // configure identity server with in-memory stores, keys, clients and scopes
    var identityServerConfig = services.AddIdentityServer()
        .AddConfigurationStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)))
        .AddOperationalStore(builder =>
            builder.UseSqlServer(connectionString, options =>
                options.MigrationsAssembly(migrationsAssembly)))
        .AddSigningCredential(new X509Certificate2(Path.Combine(_environment.ContentRootPath, "certs", "IdentityServer4Auth.pfx"), "test"));

    identityServerConfig.Services.AddTransient<IResourceOwnerPasswordValidator, ActiveDirectoryPasswordValidator>();
    identityServerConfig.Services.AddTransient<IProfileService, CustomProfileService>();
    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    InitializeDatabase(app);
    app.UseDeveloperExceptionPage();
    app.UseIdentityServer();
    app.UseMvcWithDefaultRoute();
}

QuickstartIdentityServer Config.cs (that I used to seed my database)

public class Config
{
    // scopes define the API resources in your system
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
            {
                Scopes = new [] { new Scope("api1"), new Scope("offline_access") },
                UserClaims = { ClaimTypes.Role, "user" }
            }
        };
    }

    // client want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients()
    {
        // client credentials client
        return new List<Client>
        {
            new Client
            {
                ClientId = "client",
                AllowedGrantTypes = GrantTypes.ClientCredentials,

                ClientSecrets =
                {
                    new Secret("secret".Sha256())
                },
                AllowedScopes = { "api1" }
            },

            // resource owner password grant client
            new Client
            {
                ClientId = "ro.client",
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                ClientSecrets = 
                {
                    new Secret("secret".Sha256())
                },
                UpdateAccessTokenClaimsOnRefresh = true,
                AllowedScopes = { "api1", "offline_access" },
                AbsoluteRefreshTokenLifetime = 86400,
                AllowOfflineAccess = true,
                RefreshTokenUsage = TokenUsage.ReUse
            }
        };
    }
}

WebAPI2 Startup.cs

public void Configuration(IAppBuilder app)
{
    HttpConfiguration config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
    {
        Authority = "http://localhost:44340/",
        RequiredScopes = new[] { "api1" },
        DelayLoadMetadata = true
    });

    WebApiConfig.Register(config);
    app.UseWebApi(config);
}

WebAPI2 TestController

public class TestController : ApiController
{
    // GET: api/Test
    [Authorize]
    public async Task<IHttpActionResult> Get()
    {
        return Json(new { Value1 = "value1", Value2 = "value2" });
    }
}

ConsoleApplication to test this:

private static async Task MainAsync()
{
    // discover endpoints from metadata
    //DiscoveryClient client = new DiscoveryClient("https://dev-ea-authapi");
    DiscoveryClient client = new DiscoveryClient("http://localhost:44340/");
    client.Policy.RequireHttps = false;
    var disco = await client.GetAsync();

    // request token
    var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
    var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("likosto", "CrM75fnza%");

    if (tokenResponse.IsError)
    {
        Console.WriteLine(tokenResponse.Error);
        return;
    }



    Console.WriteLine(tokenResponse.Json);
    Console.WriteLine("\n\n");

    //var newTokenResponse = await tokenClient.RequestRefreshTokenAsync(tokenResponse.RefreshToken);

    // call api
    var httpClient = new HttpClient();
    httpClient.SetBearerToken(tokenResponse.AccessToken);

    var response = await httpClient.GetAsync("http://localhost:21715/api/test");
    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine(response.StatusCode);
        // HTTP StatusCode = 400 HERE <======================
    }
    else
    {
        var content = response.Content.ReadAsStringAsync().Result;
        Console.WriteLine(JArray.Parse(content));
    }
}
1 Answers
Related