Abhishek Neogi
Abhishek Neogi

Reputation: 53

Identity Server 4

Beginner level query alert. IdentityServer4 Tutorial After going through the tutorials what I inferred was that-

  1. I create an authorization server, whose job is to issue token for the client with proper authentication.
  2. My Authorization Server runs first, and includes information and definitions of the API and client.
  3. The API has an authentication middleware that validates the incoming token to make sure if its coming from a trusted source and also its scope.
  4. The client requests a token from the authorization server and then sends request to the API with the token received.

For all this, I had to run the authorization server first, the API next and then the Client. My requirement is that I don't need a start and stop server which runs separately to take care of authentication. I have one API and I need it to double as the authorization server too. Is this possible? Is it possible for the API to generate tokens, validate them and then tend to the requests, all the while using IdentityServer4.

Upvotes: 3

Views: 2752

Answers (1)

Jeroen
Jeroen

Reputation: 63698

Update Jan 2020: For a ASP.NET Core 3.1 example of using IdentityServer4 in the same project as ASP.NET Core API controllers, you can have a look at my IdentityServer4 with MVC Controllers and AppInsights sample repo. It's goal was to test AppInsights, but it does demonstrate a SPA stub that calls both OpenID endpoints (⚠ in a non-recommended wa, using client credentials), and controller endpoints.


Although typically the Auth Server will be separate from the Resource Server, this doesn't need to be the case. You can just add all of it to one application. Here's an example.

  1. Create a new ASP.NET Core (I used 2.0) Web API application.
  2. Install-Package IdentityServer4 -Version 2.0.0-rc1 (at the time of writing rc1 is the version with .NET Core 2.x support)
  3. Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
  4. Set [Authorize] on ValuesController from the template
  5. Add this code to Configure(...) in class Startup above app.UseMvc():

    // calls app.UseAuthentication() for us
    // See: http://docs.identityserver.io/en/release/quickstarts/6_aspnet_identity.html
    app.UseIdentityServer();
    
  6. Add this code to ConfigureServices(...) in class Startup:

    services.AddIdentityServer()
        .AddDeveloperSigningCredential()
        .AddInMemoryApiResources(new[]
        {
            new ApiResource
            {
                Name = "MyApi",
                ApiSecrets = { new Secret("supersecret".Sha256()) },
                Scopes = { new Scope("myapi") },
            }
        })
        .AddInMemoryClients(new[]
        {
            new Client
            {
                ClientId = "api",
                ClientSecrets = { new Secret("supersecret".Sha256()) },
                AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                AllowedScopes = { "myapi" },
            }
        })
        .AddTestUsers(new List<TestUser>
        {
            new TestUser
            {
                SubjectId = "some-unique-id-12345678980",
                Username = "john",
                Password = "123456"
            }
        });
    
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(opts =>
        {
            opts.Authority = "http://localhost:51689";
            opts.Audience = "MyApi";
            opts.RequireHttpsMetadata = !env.IsDevelopment();
        });
    

If you now F5 the app it will show an empty page because of a "401 Unauthorized" response. You can also now check this endpoint: http://localhost:51689/.well-known/openid-configuration (with your dev port of course).

You can also do this now:

curl -X POST \
  http://localhost:51689/connect/token \
  -H 'authorization: Basic YXBpY2xpZW50aWQ6c3VwZXJzZWNyZXQ=' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'username=john&password=123456&grant_type=password'

Note that the authorization header contains a base64 encoded string representing the string "apiclientid:supersecret". This should give you a result like this:

{
    "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjczODhkMjY0MDg4Y2NjOGRiZTcwODIzZGIxYzY3ZWNkIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDUwODE3OTAsImV4cCI6MTUwNTA4NTM5MCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MTY4OSIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjUxNjg5L3Jlc291cmNlcyIsIk15QXBpIl0sImNsaWVudF9pZCI6ImFwaWNsaWVudGlkIiwic3ViIjoic29tZS11bmlxdWUtaWQtMTIzNDU2Nzg5ODAiLCJhdXRoX3RpbWUiOjE1MDUwODE3OTAsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsibXlhcGkiXSwiYW1yIjpbInB3ZCJdfQ.sxWodlJKDJgjoOj-8njZ8kONOqiKgj3E5YlKXGX5cz-WqUK7RHKJacNX09D00Y8YtmZpkc5OrY0xzOx7UuSAtDku4oOX_1o38XEGJPBSJHdjqgVGSOU-hwDkzin8HSRJ0Kna1vM3ZzTh80cFTVhP8h903GAPRrAyV8PtRXnwV0CPel8NdvML6dV-mfDpGi0l7crp-TPnH4nIG0olpRYUPV5EsgCVMG9vswnOnKz3RPOGaU8yJy7_9mbQW5GHKfN0J6swiSt5rY3NKs_t1P9-tnCDKBOAafaXjLEO3Kx4fP4xTgwK92uKcEDDnRZo_-T0CkBxnSQm0oz1sUyrW8_3Pg",
    "expires_in": 3600,
    "token_type": "Bearer"
}

In addition to the option of switching to other authentication flows, you can also add a controller method like this:

[Route("api/token")]
public class TokenController
{
    [HttpPost("request")]
    public async Task<JObject> Request(string username, string password)
    {
        var tokenClient = new TokenClient("http://localhost:51689/connect/token", "apiclientid", "supersecret");
        var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync(username, password);

        if (tokenResponse.IsError) { /* Log failed login attempt! */ } 

        return tokenResponse.Json;
    }
}

And then call it like this:

curl -X POST \
  http://localhost:51689/api/token/request \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d 'username=john&password=123456'

This should give a similar response as above.

You can now provide this access_token insde a header Authorization: Bearer access_token_should_go_here like this:

curl -X GET \
  http://localhost:51689/api/values \
  -H 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjczODhkMjY0MDg4Y2NjOGRiZTcwODIzZGIxYzY3ZWNkIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MDUwODIyODQsImV4cCI6MTUwNTA4NTg4NCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MTY4OSIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjUxNjg5L3Jlc291cmNlcyIsIk15QXBpIl0sImNsaWVudF9pZCI6ImFwaWNsaWVudGlkIiwic3ViIjoic29tZS11bmlxdWUtaWQtMTIzNDU2Nzg5ODAiLCJhdXRoX3RpbWUiOjE1MDUwODIyODQsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsibXlhcGkiXSwiYW1yIjpbInB3ZCJdfQ.hQ60zzEbZOSVpP54yGAnnzfVEks18YXn3gU2wfFgNB33UxQabk1l3xkaeUPTpuFdmFTm4TbVatPaziGqaxjzYgfdVoAwQ3rYJMuYzOh0kUowKxXTkquAlD13ScpvxrGeCXGxFTRHrxX2h-1hHGQ9j2y2f3-ESynzrCdxp5HEH1271BSYfQ7pZIzvyxxpbmOzzKDzdYfcJV6ocnOU4jXBhw6iOzqpR03zxxtjIjGbJd2QwWklBGqZlO_thdZZFi-t7zu5eC4wqRCYGGZYWOUC17_Btc_Irg2SsvLCUDzsaBw7AVgLpZ7YjF-RsVqIi6oxNQ2K0zllzUy8VbupbWKr5Q' \
  -H 'cache-control: no-cache' \

And now you should get past the [Authorize] atribute. Yay!

You now have one web application, which acts as both an Auth Server and a Resource Server.


Fun fact: with the above example the AddJwtBearer options specify the application's own url as an Authority, making the app request from itself the public key to use for validating the tokens. You could instead also use code to directly provide this key to the authentication middleware.

Upvotes: 8

Related Questions