IdentityServer / IdentityServer4

OpenID Connect and OAuth 2.0 Framework for ASP.NET Core
https://identityserver.io
Apache License 2.0
9.23k stars 4.02k forks source link

SwaggerUI not showing endpoints #2286

Closed DotNetRockStar closed 6 years ago

DotNetRockStar commented 6 years ago

Hi there. I have the Identity Server configured and I want to show the API details using Swagger (specifically using Swashbuckle). How might we expose the existing APIs that the server offers, like token api, using Swagger? Is this supported?

Thanks in advance.

leastprivilege commented 6 years ago

I don't know anything about Swagger - but we don't use e.g. MVC for our endpoints. IOW - you would need to do that manually.

I don't see a lot of sense in doing that though. The documentation is the spec and if you need a tool to "play around", create a couple of scripts or use something like Postman.

DotNetRockStar commented 6 years ago

There is a lot of value in providing swagger for your API. I highly reccomend you check out OpenApi. It serves as documentation as well as an api test harness which is very valuable when working with developers who dont understand the specification and just want to use the thing. If the endpoints were created using an MVC controller then the framework will automatically provide documentation constructs for it for free, however it doesn't seem to be an actual controller.

I have a team of 30+ people (and growing) that are going to need to integrate with my identity server and having this sort of documentation and test harness is going to be very important. We are not all using a single language/framework either, so all of the helper classes you have won't help my teams that are using Java, Node, Python, etc... That's where swagger would be huge.

Thanks for the reply. I'll see what I can do about manually doing something to get it.

brockallen commented 6 years ago

I think it might be big problem to just document OIDC/OAuth2 with OpenApi. If consumers first know OIDC/OAuth2, then there's no real need for OpenApi, and if there is then people should know where the specs are.

DotNetRockStar commented 6 years ago

It wouldn't be too difficult to provide automatic documentation if you leveraged some documentation techniques and technologies like I was hoping to find.

I agree with you about knowing what the spec is, where the docs are, and reading them. Unfortunately, in practice, this is simply not going to happen for most software developers. They just want/need to know how to integrate with it.

Like you said, it might just be an exercise in manually creating something and linking docs and specs. I'll give that a shot. Thanks

brockallen commented 6 years ago

They just want/need to know how to integrate with it.

But that's what I mean by understanding the specs. This stuff is complicated, yes, but if folks don't appreciate the value and necessity in taking that time to learn some of if and how to do it the right way, then their software and companies will be the next front page story on how shitty our industry's security practices are.

acarlstein commented 6 years ago

I am having a similar issue.

Even do swagger-jsdoc does recognize the endpoints I declared in the JS files: paths: { '/api-gateway-express/health': { get: [Object] }, '/api-gateway-express/info': { get: [Object] }, '/api-gateway-express/version': { get: [Object] } },

when I pass it to the swagger-ui-express as a parameter those endpoints are not displayed on the page. Its like swagger-ui-express ignores the parameter completely.

gatewayExpressApp.use('/api-gateway-express/api', swaggerUi.serve, swaggerUi.setup(swaggerJSDocJSON);

(For those who wonder, I am trying to do the same thing that can be done with restify-swagger-jsdoc)

brockallen commented 6 years ago

FWIW, a recent blog post on supporting swagger in IdSvr: https://www.scottbrady91.com/Identity-Server/ASPNET-Core-Swagger-UI-Authorization-using-IdentityServer4

MichelZ commented 6 years ago

@brockallen This is to get IdSvr to play along with Swagger in terms of authentication. What's requested in this issue here though is that the IdSvr API's are provided in OpenAPI format so that they can be integrated into the OpenAPI documentation. It makes things more discoverable in general. (If they see a UserInfo endpoint there, they know that there "is something", and they can google/ask for more information. I find these things extremely helpful.)

karpikpl commented 5 years ago

Hey @DotNetRockStar - where you able to add /connect API to swagger manually? I need to do the same thing and I was wondering what would be the easiest way...

DotNetRockStar commented 5 years ago

@karpikpl unfortunately I did not. :(

thehaseebahmed commented 5 years ago

@leastprivilege Please reopen this issue! This is a must-have if you're using some sort of API Gateway which uses OpenAPI specs to discover all the available endpoints. For now, I might be able to manually add them but I'd rather my API Gateway picked them via OpenAPI specs than me having to set them up manually.

@brockallen Shitty practices or bad developers is besides the topic for this issue I think. Not everyone can be Awesome! there will be people who are lazy, there will be people who take time to grasp concepts and there will be other things. That is what makes us human :-)

leastprivilege commented 5 years ago

if you know a way to add Open API descriptions for the standard OAuth/OIDC endpoints, let us know then we can discuss this. @haseebahmed7

MichelZ commented 5 years ago

I doubt that there is a "nice" way of doing this, as the most popular swagger extensions rely on ApiExplorer, which in turn relies on the whole MVC routing / modelbinding to generate the Swagger doc.

The best bet is probably to just include a static OpenAPI spec with IdSrv. It won't change much anyhow, right? :)

On a side question: What's the reason you went with your own endpoint router? Were there technical limitations?

brockallen commented 5 years ago

On a side question: What's the reason you went with your own endpoint router? Were there technical limitations?

Because any config we would need to do to MVC would affect any other MVC code in the same host. In short, ASP.NET Core is not designed to allow multiple instances of MVC (or other similar frameworks) due to the global DI/config architecture.

thehaseebahmed commented 5 years ago

I doubt that there is a "nice" way of doing this, as the most popular swagger extensions rely on ApiExplorer, which in turn relies on the whole MVC routing / modelbinding to generate the Swagger doc.

The best bet is probably to just include a static OpenAPI spec with IdSrv. It won't change much anyhow, right? :)

On a side question: What's the reason you went with your own endpoint router? Were there technical limitations?

Exactly what I had in mind! Maybe I can make a static one and create a PR for it :-) @leastprivilege What do you say?

leastprivilege commented 5 years ago

sounds good!

amadard commented 5 years ago

Exactly what I had in mind! Maybe I can make a static one and create a PR for it :-) @leastprivilege What do you say?

@haseebahmed7 Did you work on this? If not, I think I am going to work on it in the next week.

guylevy commented 4 years ago

@amadard , @haseebahmed7 - did you implement this at the end? It would be really great to have

amadard commented 4 years ago

@guylevy I got started on it, but never finished it. You asking is pushing me to think I should start a branch to build it out so incremental progress isn't lost.

nickelbob commented 4 years ago

@amadard were you able to make a dent in this? I'd love to help out if you need it.

nickelbob commented 4 years ago

Hoping someone will get annoyed by how bad this is and help me fix it up :) -

using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Utils.Swagger
{
    public class IdServer4Fakes : IDocumentFilter
    {
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            var tokenPath = new OpenApiPathItem()
            {
                Description = "Token endpoint",
            };
            tokenPath.AddOperation(OperationType.Post, new OpenApiOperation()
            {
                Tags = new[] { new OpenApiTag() { Name = "Auth" } },
                RequestBody = new OpenApiRequestBody()
                {
                    Content =
                    {
                        ["application/x-www-form-urlencoded"] = new OpenApiMediaType()
                        {
                            Example = new OpenApiString("grant_type=password&client_id=js&username=blah&password=blah&client_secret=blah"),
                            Schema = new OpenApiSchema()
                            {
                                Reference = new OpenApiReference()
                                {
                                    Id = "ref",
                                    Type = ReferenceType.RequestBody
                                }
                            }
                        }
                    },
                },
                Responses = new OpenApiResponses()
                {
                    ["200"] = new OpenApiResponse
                    {
                        Description = "OK",
                        Content =
                        {
                            ["application/json"] = new OpenApiMediaType()
                            {
                                Example = new OpenApiString("{\"access_token\":\"string\",\"expires_in\":86400,\"token_type\":\"Bearer\",\"refresh_token\":\"string\"}"),
                                Schema = new OpenApiSchema()
                            {
                                Reference = new OpenApiReference()
                                {
                                    Id = "ref",
                                    Type = ReferenceType.RequestBody
                                }
                            }
                            }
                        }
                    }
                }
            }) ;
            swaggerDoc.Paths.Add("/connect/token/", tokenPath);
        }
    }
}
amadard commented 4 years ago

@nickelbob I started building it out using Microsoft.OpenAPI to build a code first representation that could be delivered up using a RouteBuilder.MapGet.

What is everyone else's thoughts on this?

As I was writing this up, I saw @nickelbob responded, it seems we are on the same path!

E.g.


return new OpenApiDocument
{
    Info = new OpenApiInfo
    {
        Version = version,
        Title = "IdentityServer4",
    },
    Components = new OpenApiComponents()
    {
        Schemas = new Dictionary<string, OpenApiSchema>
        {
            ["DiscoveryDocumentType"] = new Microsoft.OpenApi.Readers.OpenApiStringReader().ReadFragment<OpenApiSchema>(
                "{\"type\":\"object\",\"properties\":{\"issuer\":{\"type\":\"string\"},\"jwks_uri\":{\"type\":\"string\"},\"authorization_endpoint\":{\"type\":\"string\"},\"token_endpoint\":{\"type\":\"string\"},\"userinfo_endpoint\":{\"type\":\"string\"},\"end_session_endpoint\":{\"type\":\"string\"},\"check_session_iframe\":{\"type\":\"string\"},\"revocation_endpoint\":{\"type\":\"string\"},\"introspection_endpoint\":{\"type\":\"string\"},\"device_authorization_endpoint\":{\"type\":\"string\"},\"frontchannel_logout_supported\":{\"type\":\"boolean\"},\"frontchannel_logout_session_supported\":{\"type\":\"boolean\"},\"backchannel_logout_supported\":{\"type\":\"boolean\"},\"backchannel_logout_session_supported\":{\"type\":\"boolean\"},\"scopes_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"claims_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"grant_types_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"response_types_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"response_modes_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"token_endpoint_auth_methods_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"id_token_signing_alg_values_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"subject_types_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"code_challenge_methods_supported\":{\"uniqueItems\":false,\"type\":\"array\",\"items\":{\"type\":\"string\"}},\"request_parameter_supported\":{\"type\":\"boolean\"}},\"required\":[\"issuer\",\"jwks_uri\",\"authorization_endpoint\",\"token_endpoint\",\"userinfo_endpoint\",\"end_session_endpoint\",\"check_session_iframe\",\"revocation_endpoint\",\"introspection_endpoint\",\"device_authorization_endpoint\",\"frontchannel_logout_supported\",\"frontchannel_logout_session_supported\",\"backchannel_logout_supported\",\"backchannel_logout_session_supported\",\"scopes_supported\",\"claims_supported\",\"grant_types_supported\",\"response_types_supported\",\"response_modes_supported\",\"token_endpoint_auth_methods_supported\",\"id_token_signing_alg_values_supported\",\"subject_types_supported\",\"code_challenge_methods_supported\",\"request_parameter_supported\"]}",
                Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0, out var discoveryDiagnostic)
        }
    },
    Servers = new List<OpenApiServer>
    {
        new OpenApiServer { Url = authority}
    },
    Paths = new OpenApiPaths
    {
        ["/.well-known/openid-configuration"] = new OpenApiPathItem
        {
            Description = "The discovery endpoint can be used to retrieve metadata about your IdentityServer - it returns information like the issuer name, key material, supported scopes etc.",
            Operations = new Dictionary<OperationType, OpenApiOperation>
            {
                [OperationType.Get] = new OpenApiOperation
                {
                    Description = "Retrieve metadata about your IdentityServer",
                    ExternalDocs = new OpenApiExternalDocs()
                    {
                        Description = "Discovery Endpoint",
                        Url = new System.Uri("http://docs.identityserver.io/en/latest/endpoints/discovery.html")
                    },
                    Parameters = new List<OpenApiParameter>(),
                    Responses = new OpenApiResponses
                    {
                        ["200"] = new OpenApiResponse
                        {
                            Description = "OK",
                            Content = new Dictionary<string, OpenApiMediaType>()
                            {
                                ["application/json"] = new OpenApiMediaType()
                                {
                                    Example = new OpenApiString("{\"issuer\":\"https://demo.identityserver.io\",\"jwks_uri\":\"https://demo.identityserver.io/.well-known/openid-configuration/jwks\",\"authorization_endpoint\":\"https://demo.identityserver.io/connect/authorize\",\"token_endpoint\":\"https://demo.identityserver.io/connect/token\",\"userinfo_endpoint\":\"https://demo.identityserver.io/connect/userinfo\",\"end_session_endpoint\":\"https://demo.identityserver.io/connect/endsession\",\"check_session_iframe\":\"https://demo.identityserver.io/connect/checksession\",\"revocation_endpoint\":\"https://demo.identityserver.io/connect/revocation\",\"introspection_endpoint\":\"https://demo.identityserver.io/connect/introspect\",\"device_authorization_endpoint\":\"https://demo.identityserver.io/connect/deviceauthorization\",\"frontchannel_logout_supported\":true,\"frontchannel_logout_session_supported\":true,\"backchannel_logout_supported\":true,\"backchannel_logout_session_supported\":true,\"scopes_supported\":[\"openid\",\"profile\",\"email\",\"api\",\"policyserver.runtime\",\"policyserver.management\",\"offline_access\"],\"claims_supported\":[\"sub\",\"name\",\"family_name\",\"given_name\",\"middle_name\",\"nickname\",\"preferred_username\",\"profile\",\"picture\",\"website\",\"gender\",\"birthdate\",\"zoneinfo\",\"locale\",\"updated_at\",\"email\",\"email_verified\"],\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"implicit\",\"password\",\"urn:ietf:params:oauth:grant-type:device_code\"],\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"id_token token\",\"code id_token\",\"code token\",\"code id_token token\"],\"response_modes_supported\":[\"form_post\",\"query\",\"fragment\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"subject_types_supported\":[\"public\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"request_parameter_supported\":true}"),
                                    Schema = new OpenApiSchema()
                                    {
                                        Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "DiscoveryDocumentType" }
                                    }
                                },
                            },
                        }
                    }
                }
            }
        },
    },
};

And setting up the route in Startup:


private void ConfigureSwagger(IApplicationBuilder app, IHostingEnvironment env)
{
    Task swaggerJSON(HttpContext httpContext)
    {
        httpContext.Response.StatusCode = (int)HttpStatusCode.OK;
        return httpContext.Response.WriteAsync(
            IdentityServerSwagger.GetSwaggerDocument("https://demo.identityserver.io", "v1")
            .Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json));
    }
    var rb = new RouteBuilder(app);
    rb.MapGet("/swagger/v1/swagger.json", swaggerJSON);
    var routes = rb.Build();
    app.UseRouter(routes);

    if (env.IsDevelopment())
    {
        app.UseSwaggerUI(c =>
        {
            c.SwaggerEndpoint("/swagger/v1/swagger.json", "IdentityServer4");
        });
    }
}
amadard commented 4 years ago

@nickelbob I started a branch on my personal fork. The OpenAPIDocument has the Discovery endpoint structured out, look it over and if you see any other properties that should be have detail, point it out! I need to review the DiscoveryResponseGenerator to make sure my model matches the default implementation.

My thought is that IdentityServer would just generate the swagger.json file. That way people can use whatever Swagger UI they want.

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.