Skip to content

Instantly share code, notes, and snippets.

@DanielLarsenNZ
Last active December 9, 2022 06:38
Show Gist options
  • Save DanielLarsenNZ/ac55fc6f3331623b8b527fa3d887a1f3 to your computer and use it in GitHub Desktop.
Save DanielLarsenNZ/ac55fc6f3331623b8b527fa3d887a1f3 to your computer and use it in GitHub Desktop.
SwaggerConfig.cs for OAuth2 in ASP.NET Web API to enable Swagger UI auth with Azure AD
/*
The MIT License (MIT)
Copyright (c) 2016 Daniel Larsen
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
<!-- copy these into web.config appSettings -->
<add key="SwaggerUIReplyUrl" value="https://localhost:(port)/swagger/ui/o2c-html"/>
<add key="SwaggerUIOAuth2Resource" value="(AppUriOrClientIdOfResourceToAuthWith)"/>
<add key="SwaggerUIOAuth2AuthorizationUrl" value="https://login.microsoftonline.com/(tenantId)/oauth2/authorize"/>
<add key="SwaggerUIOAuth2TokenUrl" value="https://login.microsoftonline.com/(tenantId)/oauth2/token"/>
<add key="SwaggerUIOAuth2AccessAppName" value="(ApiAppName)"/>
<add key="SwaggerUIClientId" value="(SwaggerAppClientId)"/>
<add key="SwaggerUIClientSecret" value="(SwaggerAppClientSecret)"/>
<add key="SwaggerUIAppName" value="(SwaggerAppName)"/>
*/
using System.Globalization;
using System.Linq;
using System.Web.Http;
using System.Web.Http.Description;
using Swashbuckle.Application;
using Swashbuckle.Swagger;
using WebActivatorEx;
using Quickstart.Api;
using System.Collections.Generic;
using System;
[assembly: PreApplicationStartMethod(typeof(SwaggerConfig), "Register")]
//TODO: Change Namespace
namespace Quickstart.Api
{
public class SwaggerConfig
{
public static void Register()
{
var thisAssembly = typeof(SwaggerConfig).Assembly;
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
// Use "SingleApiVersion" to describe a single version API. Swagger 2.0 includes an "Info" object to
// hold additional metadata for an API. Version and title are required but you can also provide
// additional fields by chaining methods off SingleApiVersion.
//
// TODO: Change API Name
c.SingleApiVersion("v1", "Quickstart.Api");
// If your API has multiple versions, use "MultipleApiVersions" instead of "SingleApiVersion".
// In this case, you must provide a lambda that tells Swashbuckle which actions should be
// included in the docs for a given API version. Like "SingleApiVersion", each call to "Version"
// returns an "Info" builder so you can provide additional metadata per API version.
//
//c.MultipleApiVersions(
// (apiDesc, targetApiVersion) => ResolveVersionSupportByRouteConstraint(apiDesc, targetApiVersion),
// (vc) =>
// {
// vc.Version("v2", "Swashbuckle Dummy API V2");
// vc.Version("v1", "Swashbuckle Dummy API V1");
// });
// You can use "BasicAuth", "ApiKey" or "OAuth2" options to describe security schemes for the API.
// See https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md for more details.
// NOTE: These only define the schemes and need to be coupled with a corresponding "security" property
// at the document or operation level to indicate which schemes are required for an operation. To do this,
// you'll need to implement a custom IDocumentFilter and/or IOperationFilter to set these properties
// according to your specific authorization implementation
//
//c.BasicAuth("basic")
// .Description("Basic HTTP Authentication");
//
//c.ApiKey("apiKey")
// .Description("API Key Authentication")
// .Name("apiKey")
// .In("header");
//
var authUrl = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIOAuth2AuthorizationUrl"];
var tokenUrl = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIOAuth2TokenUrl"];
var appName = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIOAuth2AccessAppName"];
if (string.IsNullOrEmpty(authUrl) || string.IsNullOrEmpty(appName))
{
throw new InvalidOperationException("App Settings \"SwaggerUIOAuth2AuthorizationUrl\" and \"SwaggerUIOAuth2AccessAppName\" must be set.");
}
if (string.IsNullOrEmpty(tokenUrl))
{
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(authUrl)
.Scopes(scopes =>
{
scopes.Add("user_impersonation", $"Access {appName}");
});
}
else
{
c.OAuth2("oauth2")
.Description("OAuth2 Implicit Grant")
.Flow("implicit")
.AuthorizationUrl(authUrl)
.TokenUrl(tokenUrl)
.Scopes(scopes =>
{
scopes.Add("user_impersonation", $"Access {appName}");
});
}
// Set this flag to omit descriptions for any actions decorated with the Obsolete attribute
//c.IgnoreObsoleteActions();
// Each operation be assigned one or more tags which are then used by consumers for various reasons.
// For example, the swagger-ui groups operations according to the first tag of each operation.
// By default, this will be controller name but you can use the "GroupActionsBy" option to
// override with any value.
//
//c.GroupActionsBy(apiDesc => apiDesc.HttpMethod.ToString());
// You can also specify a custom sort order for groups (as defined by "GroupActionsBy") to dictate
// the order in which operations are listed. For example, if the default grouping is in place
// (controller name) and you specify a descending alphabetic sort order, then actions from a
// ProductsController will be listed before those from a CustomersController. This is typically
// used to customize the order of groupings in the swagger-ui.
//
//c.OrderActionGroupsBy(new DescendingAlphabeticComparer());
// If you annotate Controllers and API Types with
// Xml comments (http://msdn.microsoft.com/en-us/library/b2s063f7(v=vs.110).aspx), you can incorporate
// those comments into the generated docs and UI. You can enable this by providing the path to one or
// more Xml comment files.
//
//c.IncludeXmlComments(GetXmlCommentsPath());
// Swashbuckle makes a best attempt at generating Swagger compliant JSON schemas for the various types
// exposed in your API. However, there may be occasions when more control of the output is needed.
// This is supported through the "MapType" and "SchemaFilter" options:
//
// Use the "MapType" option to override the Schema generation for a specific type.
// It should be noted that the resulting Schema will be placed "inline" for any applicable Operations.
// While Swagger 2.0 supports inline definitions for "all" Schema types, the swagger-ui tool does not.
// It expects "complex" Schemas to be defined separately and referenced. For this reason, you should only
// use the "MapType" option when the resulting Schema is a primitive or array type. If you need to alter a
// complex Schema, use a Schema filter.
//
//c.MapType<ProductType>(() => new Schema { type = "integer", format = "int32" });
// If you want to post-modify "complex" Schemas once they've been generated, across the board or for a
// specific type, you can wire up one or more Schema filters.
//
//c.SchemaFilter<ApplySchemaVendorExtensions>();
// In a Swagger 2.0 document, complex types are typically declared globally and referenced by unique
// Schema Id. By default, Swashbuckle does NOT use the full type name in Schema Ids. In most cases, this
// works well because it prevents the "implementation detail" of type namespaces from leaking into your
// Swagger docs and UI. However, if you have multiple types in your API with the same class name, you'll
// need to opt out of this behavior to avoid Schema Id conflicts.
//
//c.UseFullTypeNameInSchemaIds();
// Alternatively, you can provide your own custom strategy for inferring SchemaId's for
// describing "complex" types in your API.
//
//c.SchemaId(t => t.FullName.Contains('`') ? t.FullName.Substring(0, t.FullName.IndexOf('`')) : t.FullName);
// Set this flag to omit schema property descriptions for any type properties decorated with the
// Obsolete attribute
//c.IgnoreObsoleteProperties();
// In accordance with the built in JsonSerializer, Swashbuckle will, by default, describe enums as integers.
// You can change the serializer behavior by configuring the StringToEnumConverter globally or for a given
// enum type. Swashbuckle will honor this change out-of-the-box. However, if you use a different
// approach to serialize enums as strings, you can also force Swashbuckle to describe them as strings.
//
//c.DescribeAllEnumsAsStrings();
// Similar to Schema filters, Swashbuckle also supports Operation and Document filters:
//
// Post-modify Operation descriptions once they've been generated by wiring up one or more
// Operation filters.
//
//c.OperationFilter<AddDefaultResponse>();
//
// If you've defined an OAuth2 flow as described above, you could use a custom filter
// to inspect some attribute on each action and infer which (if any) OAuth2 scopes are required
// to execute the operation
//
c.OperationFilter<AssignOAuth2SecurityRequirements>();
//
// Set filter to eliminate duplicate operation ids from being generated
// when there are multiple operations with the same verb in the API.
// ***
// If you would prefer to globally impact the Swagger operation id's rather
// than control them on a per-action method basis, uncomment the next line
// and the IncludeParameterNamesInOperationIdFilter class below.
// ***
//c.OperationFilter<IncludeParameterNamesInOperationIdFilter>();
//
// Post-modify the entire Swagger document by wiring up one or more Document filters.
// This gives full control to modify the final SwaggerDocument. You should have a good understanding of
// the Swagger 2.0 spec. - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md
// before using this option.
//
//c.DocumentFilter<ApplyDocumentVendorExtensions>();
// In contrast to WebApi, Swagger 2.0 does not include the query string component when mapping a URL
// to an action. As a result, Swashbuckle will raise an exception if it encounters multiple actions
// with the same path (sans query string) and HTTP method. You can workaround this by providing a
// custom strategy to pick a winner or merge the descriptions for the purposes of the Swagger docs
//
//c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
// Wrap the default SwaggerGenerator with additional behavior (e.g. caching) or provide an
// alternative implementation for ISwaggerProvider with the CustomProvider option.
//
//c.CustomProvider((defaultProvider) => new CachingSwaggerProvider(defaultProvider));
// ***** Uncomment the following to enable the swagger UI *****
})
.EnableSwaggerUi(c =>
{
// By default, swagger-ui will validate specs against swagger.io's online validator and display the result
// in a badge at the bottom of the page. Use these options to set a different validator URL or to disable the
// feature entirely.
//c.SetValidatorUrl("http://localhost/validator");
//c.DisableValidator();
// If your API has multiple versions and you've applied the MultipleApiVersions setting
// as described above, you can also enable a select box in the swagger-ui, that displays
// a discovery URL for each version. This provides a convenient way for users to browse documentation
// for different API versions.
//
//c.EnableDiscoveryUrlSelector();
// If your API supports the OAuth2 Implicit flow, and you've described it correctly, according to
// the Swagger 2.0 specification, you can enable UI support as shown below.
//
var resource = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIOAuth2Resource"];
var replyUrl = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIReplyUrl"];
var clientId = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIClientId"];
var clientSecret = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIClientSecret"];
var appName = System.Configuration.ConfigurationManager.AppSettings["SwaggerUIAppName"];
if (string.IsNullOrEmpty(resource) || string.IsNullOrEmpty(replyUrl) || string.IsNullOrEmpty(clientId)
|| string.IsNullOrEmpty(clientSecret) || string.IsNullOrEmpty(appName))
{
throw new InvalidOperationException("App Settings SwaggerUIOAuth2Resource, SwaggerUIReplyUrl, SwaggerUIClientId, SwaggerUIClientKey and SwaggerUIAppName must be set.");
}
var additionalParams = new Dictionary<string, string> { { "resource", resource } };
c.EnableOAuth2Support(clientId,
clientSecret,
replyUrl, appName,
additionalQueryStringParams: additionalParams);
});
}
}
/// <summary>
/// If you would prefer to control the Swagger Operation ID
/// values globally, uncomment this class, as well as the
/// call above that wires this Operation Filter into
/// the pipeline.
/// </summary>
/*
internal class IncludeParameterNamesInOperationIdFilter : IOperationFilter
{
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
{
if (operation.parameters != null)
{
// Select the capitalized parameter names
var parameters = operation.parameters.Select(
p => CultureInfo.InvariantCulture.TextInfo.ToTitleCase(p.name));
// Set the operation id to match the format "OperationByParam1AndParam2"
operation.operationId = $"{operation.operationId}By{string.Join("And", parameters)}";
}
}
}
*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment