DEV Community 👩‍💻👨‍💻

Christos Matskas for The 425 Show

Posted on

Secure Open API (Swagger) calls with Azure Active Directory

We have talked about secure web apps and APIs many times here. In this blog post we'll examine how to secure Swashbuckle (.NET's version of Open API/Swagger) with Azure Active Directory in order to make authenticated calls to secure APIs.

Configure the Azure AD App Registrations

To enable end-to-end authentication ,we need to create 2 App Registrations in Azure AD. One for the API and one for the OpenAPI client. You can use the attached .NET Interactive Notebook app-registrations.ipynb to automatically configure both the API and the Swagger App Registrations in Azure AD.

Open API/Swagger with .NET

When you create a new API in .NET Core, the default template adds the Open API configuration by default. This means that everything works out of the box. However, if you add authentication to the API then Open Api stops working and needs additional configuration to acquire the necessary access token from Azure AD to make authenticated calls to the API.

Below we have the starting configuration of Open API in .NET

public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "VolcanoAPI", Version = "v1" });
        });
    }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "YouAPI v1"));
    }

    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
         endpoints.MapControllers();
    });
}
Enter fullscreen mode Exit fullscreen mode

Let's implement authentication for our Open API middleware and UI. First, update the Startup.cs with the necessary code. In the ConfigureServices() method, add the following code:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "swaggerAADdemo", Version = "v1" });            
    c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Description = "OAuth2.0 Auth Code with PKCE",
        Name = "oauth2",
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri(Configuration["AuthorizationUrl"]),
                TokenUrl = new Uri(Configuration["TokenUrl"]),
                Scopes = new Dictionary<string, string>
                {
                    { Configuration["ApiScope"], "read the api" }
                }
            }
        } 
    });
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
            },
            new[] { Configuration["ApiScope"] }
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

Next step, update the Configure() method to wire up the UI with the appropriate authentication settings. This bit will enable users to login in the UI before making API calls in the OpenAPI page.

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "swaggerAADdemo v1");
        c.OAuthClientId(Configuration["OpenIdClientId"]);
        c.OAuthUsePkce();
        c.OAuthScopeSeparator(" ");
    });
}
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, both bits of code require some configuration settings. Open the appsettings.json file and add the following json at the end:

"AuthorizationUrl":"https://login.microsoftonline.com/<your tenant Id>/oauth2/v2.0/authorize",
TokenUrl":"https://login.microsoftonline.com/<your tenant Id>/oauth2/v2.0/token",
"ApiScope": <your api scope(s)>,
"OpenIdClientId": "<your swagger app reg client id>"
Enter fullscreen mode Exit fullscreen mode

The APIScope property should have a value similar to this "api://cd28264c-2a31-49df-b416-bf6f332c716d/". This is the scope expected in the Access token by your API.

Finally, the OpenIdClientId should contain the Client ID from the Azure AD App Registration -> We did this as part of step 1 when we created the Azure AD App Registrations.

See it in action below:
Step 1 - Authenticate in Swagger UI
Alt Text
Step 2 - Make an authenticated call to the API
Alt Text

Source Code

As always, if you want to clone the source code, you can find a working repo on GitHub

Conclusion

I hope you found this useful but feel free to reach out to us on Twitter @christosmatskas, @AzureAndChill or join us on Discord to let us know if you have any issues with authentication, Azure AD or B2C!

Top comments (9)

Collapse
abombdotcom profile image
ABombDotCom

Kudos and thanks @christosmatskas ! This did just what I needed.

Collapse
herecomeslappy profile image
Lappy

All this can be achieved using only one single application not the two.

You create one Single Page App on Azure AD. You create a scope in that app. Make roles and assign them to your users. They you just put the same app Id for both OpenIdClientId and ClientId and so it can use the same app to authenticate as well as the API can use it.

Collapse
chrisworledge profile image
Chris Wobble

The startup/appsettings code looks fine, but the Azure AD setup is a bit mysterious

Not sure what the comment about the interactive notebook means, but I can configure an OpenAPI Client with a redirect uri of api/swagger/index. Not sure about the scopes though.... or Select the tokens you would like to be issued by the authorization endpoint:
Access tokens (used for implicit flows)
ID tokens (used for implicit and hybrid flows)

Collapse
christosmatskas profile image
Christos Matskas Author

Hi Chris, thanks for the comment. The .NET Interactive Notebook sets up 2 App Registrations. One of them is used by the API to validate incoming tokens and scopes. The second one is used by the Swagger UI to acquire an Access Token and call the API endpoints. Please note that we don't use and encourage against implicit flows at all times. The current implementation uses Auth Code with PKCE. Ping us if you have any questions

Collapse
eduardomb08 profile image
Eduardo Monteiro de Barros

Can't get this to work either. First tried setting up the App Registrations myself. Then used the provided notebook on a test tenant where I have admin rights. Nonetheless, I ended up with the same error:

Image description

Any help would be greatly appreciated!

PS: Also tried the Discord link and got a "Invite Invalid" error.

Thanks and Best Regards,

Eduardo

Collapse
eduardomb08 profile image
Eduardo Monteiro de Barros • Edited on

Hi,

I was able to get it working, but I had to use a different port. For some reason, if I use port 5001 I get the error showed above. I can repeatedly change the port on the project's config to make it work or break it.

Neither port 5001 or any other port that I use to get it to work seem to be necessary to be listed under the App Registration's Authentication tab.

I just can't figure out:
1) Isn't the redirect URL required to be listed in the Authentication tab, under Single-page application? Could it be that because I'm using localhost the port is ignored?

2) What could be causing it not to work for a specific port?

Collapse
eduardomb08 profile image
Eduardo Monteiro de Barros

Hi again,

I wanted to share this in case anyone else is having the same problem. After comparing the Authorize URLs from port 5001 with any other port, I noticed the former was missing the code_challenge attributes and the sso_reload attribute.

After poking around some more, I started suspecting some cache issue could be taking place. So, I decided to open the Swagger UI page in an Incognito tab using port 5001 (the one that was breaking the authentication).

Voilà! It worked.

Best to all!

Collapse
skanvk15 profile image
skanvk15

@christosmatskas: Thanks for the detailed article. I am unable to get this to work when API is using AD B2C. I updated the urls to this:
Authorization URL: MYB2CDOMAIN.b2clogin.com/MYB2CDOMA...
Token URL: MYB2CDOMAIN.b2clogin.com/MYB2CDOMA...
But getting this error:
AADB2C90182: The supplied code_verifier does not match associated code_challenge.
Any ideas on how to get this to work with B2C?

Collapse
pinkesh6834 profile image
Pinkesh Patel

@christosmatskas Great article, out of the curiosity why you are not passing secret in swagger?

🌚 Life is too short to browse without dark mode