Secure Web APIs With Swagger, Swashbuckle, and OAuth2 (Part 3)
This article continues the process started in [part 1(https://knowyourtoolset.com/2015/08/secure-web-apis-with-swagger-swashbuckle-and-oauth2-part-1/)] which concluded with us having an API that has both anonymous and secure methods that can be called, and a Swagger interface provided by Swashbuckle. For this post, I will be discussing how to customize the Swagger interface and make it your own. Following this, we will lock down the Swagger interface (next and final post in this series).
You really have three options to customize the Swagger interface using Swashbuckle:
- Injecting your own stylesheet (CSS) into the mix
- Injecting some javascript into the mix
- Replacing the entire html contents of the page
I’m using the first and third options above in order to achieve maximum control over the results. Since I’m not really using “inject javascript” at all, I won’t discuss that, beyond saying that you can review the DOM and manipulate as you see fit by injecting your javascript using the same approach as used for CSS and for the custom HTML.
Injecting your own stylesheet
To start with, I created a new stylesheet called Swagger.css
in the Content
folder of the API project. You can use an existing stylesheet like Site.css
or any name that you like (and place it anywhere in the project). Once you have created the file, you need to access its properties (right
click on the filename from within Visual Studio, choose Properties at the bottom of the menu) and set the Build Action to
be “Embedded Resource”. This is a very important step — your customization will not work without it.
Secondly, you need to make sure you have a line like this within the .EnableSwaggerUi
extension method call:
1c.InjectStylesheet(thisAssembly, "KeenNewApi.Content.Swagger.css");
“KeenNewApi” is the name of your project / assembly, the “Content” is the name of the subfolder you have your CSS file in. If you have not put your CSS file in a subfolder, then the string would be “KeenNewApi.Swagger.css”, and if you have multiple subfolders, each directory should be separated by a “.”.
Then it’s pretty typical CSS modification. I used the F12 developer tools to locate the top Swagger navbar which has an element like this (the … is just there to represent some stuff in between the elements):
1<div id="header">
2 ....
3</div>
I wanted to change the header bar from green (saw this in the F12 tools) to orange, so I just made my Swagger.css
look like this:
1#header {
2 background-color: orange !important;
3}
The reason for the important is because something in the core Swagger CSS still overrode this, but you get the point. You can use the DOM explorer to find the elements you want to re-style and then modify your CSS to your specs.
Since this is an “embedded resource”, just updating the CSS and refreshing the Swagger page will not have any effect. You will need to re-run the application if you want the change to take effect.
Here is the unmodified, standard Swagger UI provided by Swashbuckle:
Here is the result of injecting the custom CSS with the orange header:
Replacing the entire HTML contents of the page
The beginning to the approach here is the same as with the CSS;
- reate a new HTML page somewhere in your project (I put mine in the Content folder and called it CustomSwagger.html)
- Modify the Build Action property of the file and set it as an “Embedded Resource”
- Modify the SwaggerConfig.cs EnableSwaggerUi method to have set a CustomAsset for “index” that references your embedded resource
- Modify the HTML contents to suit your needs
For point 3 above, the line in SwaggerConfig.cs
ends up looking something like this – using the same referencing conventions as noted above:
1c.CustomAsset("index", thisAssembly, "KeenNewApi.Content.CustomSwagger.html");
The tricky part in all of this is figuring out what to make the HTML look like. To figure this out, I first tried using the sample custom HTML file from the GitHub project, but that didn’t really work for me. I then just ran the project WITHOUT a custom asset for “index” and did a “view source” on the web browser from the swagger/ui/index page. I then copy/pasted that content into the CustomSwagger.html file and started my mods from there, which worked pretty well.
I ended up with a file that looked like this (I’ll have some comments on the code below the code listing):
1<!DOCTYPE html>
2<html>
3<head>
4 <title>Swagger UI</title>
5 <link rel="icon" type="image/png" href="images/favicon-32x32-png" sizes="32x32" />
6 <link rel="icon" type="image/png" href="images/favicon-16x16-png" sizes="16x16" />
7 <link href='css/typography-css' media='screen' rel='stylesheet' type='text/css' />
8 <link href='css/reset-css' media='screen' rel='stylesheet' type='text/css' />
9 <link href='css/screen-css' media='screen' rel='stylesheet' type='text/css' />
10 <link href='css/reset-css' media='print' rel='stylesheet' type='text/css' />
11 <link href='css/print-css' media='print' rel='stylesheet' type='text/css' />
12 <link href='ext/KeenNewApi-Content-Swagger-css' media='screen' rel='stylesheet' type='text/css' />
13
14 <link href="/Content/bootstrap.css" rel="stylesheet" />
15 <script src="/Scripts/modernizr-2.6.2.js"></script>
16
17 <script src="/Scripts/jquery-1.10.2.js"></script>
18
19 <script src='lib/jquery-slideto-min-js' type='text/javascript'></script>
20 <script src='lib/jquery-wiggle-min-js' type='text/javascript'></script>
21 <script src='lib/jquery-ba-bbq-min-js' type='text/javascript'></script>
22 <script src='lib/handlebars-2-0-0-js' type='text/javascript'></script>
23 <script src='lib/underscore-min-js' type='text/javascript'></script>
24 <script src='lib/backbone-min-js' type='text/javascript'></script>
25 <script src='swagger-ui-js' type='text/javascript'></script>
26 <script src='lib/highlight-7-3-pack-js' type='text/javascript'></script>
27 <script src='lib/marked-js' type='text/javascript'></script>
28
29 <script src='lib/swagger-oauth-js' type='text/javascript'></script>
30
31 <script src="/Scripts/bootstrap.js"></script>
32 <script src="/Scripts/respond.js"></script>
33
34 <script type="text/javascript">
35 $(function () {
36 var url = window.location.search.match(/url=([^&]+)/);
37 if (url && url.length > 1) {
38 url = decodeURIComponent(url[1]);
39 } else {
40 url = "http://petstore.swagger.io/v2/swagger.json";
41 }
42
43 // Get Swashbuckle config into JavaScript
44 function arrayFrom(configString) {
45 return (configString !== "") ? configString.split('|') : [];
46 }
47
48 function stringOrNullFrom(configString) {
49 return (configString !== "null") ? configString : null;
50 }
51
52 window.swashbuckleConfig = {
53 rootUrl: '%(RootUrl)',
54 discoveryPaths: arrayFrom('%(DiscoveryPaths)'),
55 booleanValue: arrayFrom('true|false'),
56 validatorUrl: stringOrNullFrom(''),
57 customScripts: arrayFrom(''),
58 docExpansion: '%(DocExpansion)',
59 oAuth2Enabled: '%(OAuth2Enabled)',
60 oAuth2ClientId: '%(OAuth2ClientId)',
61 oAuth2Realm: '%(OAuth2Realm)',
62 oAuth2AppName: '%(OAuth2AppName)'
63 };
64
65 window.swaggerUi = new SwaggerUi({
66 url: swashbuckleConfig.rootUrl + "/" + swashbuckleConfig.discoveryPaths[0],
67 dom_id: "swagger-ui-container",
68 booleanValues: swashbuckleConfig.booleanValues,
69 onComplete: function(swaggerApi, swaggerUi){
70 if (typeof initOAuth == "function" && swashbuckleConfig.oAuth2Enabled) {
71 initOAuth({
72 clientId: swashbuckleConfig.oAuth2ClientId,
73 realm: swashbuckleConfig.oAuth2Realm,
74 appName: swashbuckleConfig.oAuth2AppName
75 });
76 }
77 $('pre code').each(function(i, e) {
78 hljs.highlightBlock(e)
79 });
80
81 addApiKeyAuthorization();
82
83 window.swaggerApi = swaggerApi;
84 _.each(swashbuckleConfig.customScripts, function (script) {
85 $.getScript(script);
86 });
87 },
88 onFailure: function(data) {
89 log("Unable to Load SwaggerUI");
90 },
91 docExpansion: swashbuckleConfig.docExpansion,
92 sorter : "alpha"
93 });
94
95 if (window.swashbuckleConfig.validatorUrl !== '')
96 window.swaggerUi.options.validatorUrl = window.swashbuckleConfig.validatorUrl;
97
98 function addApiKeyAuthorization(){
99 var key = encodeURIComponent($('#input_apiKey')[0].value);
100 if(key && key.trim() != "") {
101 var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization("api_key", key, "query");
102 window.swaggerUi.api.clientAuthorizations.add("api_key", apiKeyAuth);
103 log("added key " + key);
104 }
105 }
106
107 $('#input_apiKey').change(addApiKeyAuthorization);
108
109 window.swaggerUi.load();
110
111 function log() {
112 if ('console' in window) {
113 console.log.apply(console, arguments);
114 }
115 }
116 });
117 </script>
118</head>
119
120<body class="swagger-section">
121 <div id="header">
122 <div class="navbar navbar-inverse navbar-fixed-top">
123 <div class="container">
124 <div class="navbar-header">
125 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
126 <span class="icon-bar"></span>
127 <span class="icon-bar"></span>
128 <span class="icon-bar"></span>
129 </button>
130 <a class="navbar-brand" href="/">Keen New API</a>
131 </div>
132 <div class="navbar-collapse collapse">
133 <ul class="nav navbar-nav">
134 <li><a href="/swagger">Swagger</a></li>
135 <li><a href="/Help">API</a></li>
136 <li class="text-right">
137 <a href="javascript:document.getElementById('logoutForm').submit()">Log out</a>
138 <form action="/Home/SignOut" id="logoutForm" method="post"></form>
139 </li>
140 </ul>
141 </div>
142 </div>
143 </div>
144 </div>
145
146 <div class="container" style="padding-top: 30px;">
147 <div class="alert alert-success alert-dismissible" role="alert">
148 <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
149 <strong>NEW! </strong>Check out our keen new authentication toggles!
150 </div>
151 </div>
152
153 <div id="swagger-ui-container" class="swagger-ui-wrap"></div>
154
155</body>
156</html>
My comments fall into three areas: (1) the head section with its references, (2) the javascript, and (3), the main html body.
The head section
For this, I wanted to make sure I could reference bootstrap classes and the latest jquery, as well as my own CSS file, so I added lines 12-17, 31-32 to accomplish that.
The javascript
It took a little tinkering, but the operative section in the javascript that you’ll specifically want to pay attention to is the
one where it calls window.swashbuckleConfig
as shown below. I think you should be able to use the code below in the place of your own version
if you copied/pasted from the standard Swagger index page. Your version likely has hard-coded values, and I wanted to make
sure that the javascript honored what I had in SwaggerConfig.cs
for these values.
1window.swashbuckleConfig = {
2 rootUrl: '%(RootUrl)',
3 discoveryPaths: arrayFrom('%(DiscoveryPaths)'),
4 booleanValue: arrayFrom('true|false'),
5 validatorUrl: stringOrNullFrom(''),
6 customScripts: arrayFrom(''),
7 docExpansion: '%(DocExpansion)',
8 oAuth2Enabled: '%(OAuth2Enabled)',
9 oAuth2ClientId: '%(OAuth2ClientId)',
10 oAuth2Realm: '%(OAuth2Realm)',
11 oAuth2AppName: '%(OAuth2AppName)'
12 };
The HTML content
In the HTML body section above, there are basically three main divs: id=”header”
, the “container” (no id), and id=”swagger-ui-container”
. You
want to make sure that you KEEP the “swagger-ui-container”, as that is where all of the real swagger stuff is going to show up.
The “header” I modified based on the _layout.cshtml
file from my MVC folders so that the nav bar looked like the rest of the site.
The id-less container is simply a bootstrap “dismissable alert” that I wanted to use to show that I had access to both the bootstrap css functionality as well as its javascript.
Here is the result of the customized HTML:
As for your own modifications, I’m guessing you have enough to go on at this point. Happy customizing!!
PS. The last post (coming shortly) will describe how to secure the Swagger interface so that only authenticated users can access it.