Azure AD Login for your SPA using React Hooks

In this tutorial we are implementing Microsoft Azure Ad Login in React single-page app and retrieve user information using @azure/msal-browser.

The MSAL library for JavaScript enables client-side JavaScript applications to authenticate users using Azure AD work and school accounts (AAD), Microsoft personal accounts (MSA) and social identity providers like Facebook, Google, LinkedIn, Microsoft accounts, etc. through Azure AD B2C service. It also enables your app to get tokens to access Microsoft Cloud services such as Microsoft Graph.

Azure application registration

You can register you web app using  Microsoft tutorial.

In the below App.js file code we are using Context and Reducer for managing the login state. useReducer allows functional components in React access to reducer functions from your state management

First we initialize the initial values for reducer,
Reducers are functions that take the current state and an action as arguments, and return a new state result. In other words, (state, action) => newState

You can see the code form 17 line where we created reducer with using switch case “LOGIN” and “LOGOUT”. and from Line 58 we use the context that defined in line 6 and passing the reducer values to it.

And In a line 66 : we are checking the state.isAuthenticated or not , if it’s not the page redirect to the home page and if it’s Authenticated it will be redirected to the admin dashboard home page.

App.Js

import React, { useReducer } from "react";
import { createBrowserHistory } from "history";
import { Router, Route, Switch, Redirect } from "react-router-dom";
import AdminLayout from "layouts/Admin.js";
import HomeLayout from "layouts/Home.js";
export const AuthContext = React.createContext();
const hist = createBrowserHistory();


const initialState = {
    isAuthenticated: false,
    user: null,
    token: null,
    
};

export const reducer = (state, action) => {
    switch (action.type) {
        case "LOGIN":
            localStorage.setItem("user", action.payload.user);
            localStorage.setItem("token", action.payload.token);
            return {
                ...state,
                isAuthenticated: true,
                user: action.payload.user,
                token: action.payload.token
            };
        case "LOGOUT":
            localStorage.clear();
            return {
                ...state,
                isAuthenticated: false,
                user: null
            };
        default:
            return state;
    }
};

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);

    React.useEffect(() => {
        const user = localStorage.getItem('user') || null;
        const token = localStorage.getItem('token') || null;

        if (user && token) {
            dispatch({
                type: 'LOGIN',
                payload: {
                    user,
                    token
                }
            })
        }
    }, [])
    return (
        <AuthContext.Provider
            value={{
                state,
                dispatch
            }}
        >
            <Router history={hist}>
                <Switch>
                    <div className="App">{!state.isAuthenticated ?
                        <>
                            <Route path="/home" render={(props) => <HomeLayout {...props} />} />
                            <Redirect to="/home" /> </> :
                        <><Route path="/admin" render={(props) => <AdminLayout {...props} />} />
                            <Redirect to="/admin/dashboard" /></>}</div>
                </Switch>
            </Router>
        </AuthContext.Provider>
    );
}

export default App;

Now we are coming the the Login Component with Azure Ad using with @azure/msal-browser

In a below code we are importing AuthContext from App.js and PublicClientApplication from @azure/msal-browser.

and In a 7th line we created constant variable “dispatch” with providing the reference of AuthContext then we created the msalInstandce variable using the PublicClientApplication and providing the clientId, authority and redirectUri in auth block.

In a line 22 we created the startlogin function then we created the loginRequest variable and providing the scope for login.

and In a line 31 we use the msalInstance.loginPopup(loginRequest) for getting pop up window for login and in a “loginRespone” variable save the login response. then we are fetching the username and token form loginRespone. dispatch to the AuthContext (see line 41).

And In a line 56 we created a button it’s call the startLogin function on click event.

Login.cs

import React from "react";

import { AuthContext } from "App";
import {  PublicClientApplication } from '@azure/msal-browser';

export const Login = () => {
  const { dispatch } = React.useContext(AuthContext);

  const msalInstance = new PublicClientApplication({

    auth: {
      clientId: "65395645-90dd-4436-84a4-47d7c0e961fc",
      authority: "https://login.microsoftonline.com/common",
      redirectUri: "https://localhost:3000",
    },
    cache: {
      cacheLocation: "sessionStorage",
      storeAuthStateInCookie: true,
           }
  });

  const startLogin = async (e) => {
    e.preventDefault();
   
    var loginRequest = {
      scopes: ["65395645-90dd-4436-84a4-47d7c0e961fc/.default"] // optional Array<string>
    };
   
    try {
      
      const loginResponse = msalInstance.loginPopup(loginRequest);
      
      const username = (await loginResponse).account.username;
      const token = (await loginResponse).accessToken;

      const resJson = {
        token: token,
        user: username
      };

      dispatch({
        type: "LOGIN",
        payload: resJson
      })
      console.log("Login successfully");
    } catch (err) {

      console.log("Authentication Failed.....");
      console.log(err);
    }
  }
 
  return (
    <div>

      <button className="btn btn-sm btn-outline-success my-2 my-sm-0" data-toggle="button" onClick={(e) => { startLogin(e) }}>Login</button>

    </div>

  );
};

export default Login;

Above you see we used the reducer and context in App.js file and crated a Login Component.

Conclusion

Now we can import the Login component anywhere in a project where we want to login button that uses the Azure AD for Authentication.

Azure AD Authentication in ASP.NET Core Web API

The next step to building an API is to protect it from anonymous users. Azure Active Directory (AD) serves as an identity platform that can be used to secure our APIs from anonymous users. After the authentication is enabled, users will need to provide a OAuth 2.0/ JST token to gain access to our API.

Let us begin to implement Azure AD Authentication in ASP.NET Core 5.0 Web API.

I will be creating ASP.NET Core 5.0 project and show you step by step how to enable authentication on it using Azure AD Authentication. We will be doing it using the MSAL package from nuget.

Prerequisites

Before you start to follow steps given in this article, you will need an Azure Account, and Visual Studio 2019 with .NET 5.0 development environment step.

Creating ASP.NET Core 5.0 web application

Open visual studio and click on Create a new project in the right and select “Asp.net core web app” as shown in below image and click next.

In the configure your new project section enter name and location of your project as shown in below image and click next

In the additional information step, select .NET 5.0 in the target framework, Authentication Type to none and check Configure HTTPS checkbox and click on create.

Configuring ASP.NET Core 5.0 App for Azure AD Authentication

Open appsettings.json of your web api and add following lines of code.

"AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "gtmcatalyst.com",  "qualified.domain.name",
    "ClientId": "your-client-id",
    "TenantId": "your-tenant-id"
  }

Replace your-client-id and your-tenant-id with the actual values that you copied while doing app registration in azure ad

Next, add package manager console and add following two package references to your web application.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

Next, open startup.cs in your project and paste following code in the ConfigureServices method

   services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
               .AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));

Next, in the Startup.cs, go to Configure method and add app.UseAuthentication(); line before app.UseAuthorization(); line.

Next, open any Controller and add [Authorize] attribute:

    [Authorize]
    [Route("[controller]")]
    [ApiController]
    public class SupportController : ControllerBase
    {

    }

Save all files and run your project.

You will notice that once you run the project, and try to access any method in support controller from the browser you will get return the HTTP ERROR 401 ( Unauthorized client error).

Conclusion

Our API is no longer available for anonymous access. It is now protected by Azure AD Authentication.

How to use NLog with ASP.NET Core 2

We can make a ASP.NET Core app without any logging. But in the real world, we should use some form of logging. In this blog post we are providing an overview of 3rd Party Logging solutions such as NLog.

NLog is a flexible and free logging platform for various .NET platforms, including .NET standard. NLog makes it easy to write to several targets. (database, file, console) and change the logging configuration on-the-fly.

NLog supports the following platforms:

  • .NET Framework 3.5, 4, 4.5 – 4.8
  • .NET Framework 4 client profile
  • Xamarin Android
  • Xamarin iOs
  • Windows Phone 8
  • Silverlight 4 and 5
  • Mono 4
  • ASP.NET 4 (NLog.Web package)
  • ASP.NET Core (NLog.Web.AspNetCore package)
  • .NET Core (NLog.Extensions.Logging package)
  • .NET Standard 1.x – NLog 4.5
  • .NET Standard 2.x – NLog 4.5
  • UWP – NLog 4.5

Getting started with ASP.NET Core 2

Following are the steps to configure NLog in ASP.NET Core application

1. Add dependency in csproj manually or using NuGet

Install the latest:

  1. PM> Install-Package NLog  
  2. PM> Install-Package NLog.Web.AspNetCore 

Or in csproj:

<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.3" />
<PackageReference Include="NLog" Version="4.7.6" />

2. Create a nlog.config file

Create nlog.config (lowercase all) file in the root of your project.

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="Info"
      internalLogFile="c:\temp\internal-nlog.txt">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- the targets to write to -->
  <targets>
    <!-- write logs to file  -->
    <target xsi:type="File" name="allfile" fileName="c:\temp\nlog-all-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />

    <!-- another file log, only own logs. Uses some ASP.NET core renderers -->
    <target xsi:type="File" name="ownFile-web" fileName="c:\temp\nlog-own-${shortdate}.log"
            layout="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}" />
  </targets>

  <!-- rules to map from logger name to target -->
  <rules>
    <!--All logs, including from Microsoft-->
    <logger name="*" minlevel="Trace" writeTo="allfile" />

    <!--Skip non-critical Microsoft logs and so log only own logs-->
    <logger name="Microsoft.*" maxlevel="Info" final="true" /> <!-- BlackHole without writeTo -->
    <logger name="*" minlevel="Trace" writeTo="ownFile-web" />
  </rules>
</nlog>

3. Enable copy to bin folder

or edit .csproj file manually and add:

<ItemGroup>
    <Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
4. Update program.cs
using NLog.Web;
using Microsoft.Extensions.Logging;

public static void Main(string[] args)
{
    // NLog: setup the logger first to catch all errors
    var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
    try
    {
        logger.Debug("init main");
        CreateWebHostBuilder(args).Build().Run(); 
    }
    catch (Exception ex)
    {
        //NLog: catch setup errors
        logger.Error(ex, "Stopped program because of exception");
        throw;
    }
    finally
    {
        // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
        NLog.LogManager.Shutdown();
    }
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureLogging(logging =>
        {
            logging.ClearProviders();
            logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
        })
        .UseNLog();  // NLog: setup NLog for Dependency injection

5. Configure appsettings.json

The Logging configuration specified in appsettings.json overrides any call to SetMinimumLevel. So either remove "Default": or adjust it correctly to your needs.

{
    "Logging": {
        "LogLevel": {
            "Default": "Trace",
            "Microsoft": "Information"
        }
    }
}

Remember to also update any environment specific configuration to avoid any surprises. Ex appsettings.Development.json

6. Write logs

Inject the ILogger in your controller:

using Microsoft.Extensions.Logging;

public class HomeController : Controller
{
    private readonly ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Index page says hello");
        return View();
    }

7. Example Output

When starting the ASP.NET Core website, we get two files:

nlog-own-2017-10-10.log

2020-12-29 14:40:29.5143||DEBUG|ASP.NET_Core_2___VS2017.Program|init main |url: |action: 
2020-12-29 14:40:32.1326|0|INFO|ASP.NET_Core_2___VS2017.Controllers.HomeController|Hello, this is the index! |url: http://localhost/|action: Index

nlog-all-2017-10-10.log

2020-12-29 14:40:29.5143||DEBUG|ASP.NET_Core_2___VS2017.Program|init main 
2020-12-29 14:40:30.9739|0|INFO|Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager|User profile is available. Using 'C:\Users\j.verdurmen\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. 
2020-12-29 14:40:30.9897|37|DEBUG|Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository|Reading data from file 'C:\Users\j.verdurmen\AppData\Local\ASP.NET\DataProtection-Keys\key-bfd1ce07-8dc6-4eef-a51a-d21ddb547109.xml'. 
2020-12-29 14:40:31.0004|18|DEBUG|Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager|Found key {bfd1ce07-8dc6-4eef-a51a-d21ddb547109}. 
2020-12-29 14:40:31.0124|13|DEBUG|Microsoft.AspNetCore.DataProtection.KeyManagement.DefaultKeyResolver|Considering key {bfd1ce07-8dc6-4eef-a51a-d21ddb547109} with expiration date 2017-12-28 19:01:07Z as default key. 
2020-12-29 14:40:31.0422|0|DEBUG|Microsoft.AspNetCore.DataProtection.TypeForwardingActivator|Forwarded activator type request from Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60 
2020-12-29 14:40:31.0422|51|DEBUG|Microsoft.AspNetCore.DataProtection.XmlEncryption.DpapiXmlDecryptor|Decrypting secret element using Windows DPAPI. 
2020-12-29 14:40:31.0422|0|DEBUG|Microsoft.AspNetCore.DataProtection.TypeForwardingActivator|Forwarded activator type request from Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60 to Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Culture=neutral, PublicKeyToken=adb9793829ddae60 
2020-12-29 14:40:31.0422|4|DEBUG|Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory|Opening CNG algorithm 'AES' from provider '(null)' with chaining mode CBC. 
2020-12-29 14:40:31.0543|3|DEBUG|Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.CngCbcAuthenticatedEncryptorFactory|Opening CNG algorithm 'SHA256' from provider '(null)' with HMAC. 
2020-12-29 14:40:31.0543|2|DEBUG|Microsoft.AspNetCore.DataProtection.KeyManagement.KeyRingProvider|Using key {bfd1ce07-8dc6-4eef-a51a-d21ddb547109} as the default key. 
2020-12-29 14:40:31.0543|0|DEBUG|Microsoft.AspNetCore.DataProtection.Internal.DataProtectionStartupFilter|Key ring with default key {bfd1ce07-8dc6-4eef-a51a-d21ddb547109} was loaded during application startup. 
2020-12-29 14:40:31.4080|3|DEBUG|Microsoft.AspNetCore.Hosting.Internal.WebHost|Hosting starting 
2020-12-29 14:40:31.5508|4|DEBUG|Microsoft.AspNetCore.Hosting.Internal.WebHost|Hosting started 
2020-12-29 14:40:31.5508|0|DEBUG|Microsoft.AspNetCore.Hosting.Internal.WebHost|Loaded hosting startup assembly ASP.NET Core 2 - VS2017 
2020-12-29 14:40:31.5526|0|DEBUG|Microsoft.AspNetCore.Hosting.Internal.WebHost|Loaded hosting startup assembly Microsoft.AspNetCore.ApplicationInsights.HostingStartup 
2020-12-29 14:40:31.5526|0|DEBUG|Microsoft.AspNetCore.Hosting.Internal.WebHost|Loaded hosting startup assembly Microsoft.AspNetCore.Server.IISIntegration 
2020-12-29 14:40:31.6909|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK64" started. 
2020-12-29 14:40:31.6909|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK65" started. 
2020-12-29 14:40:31.7418|19|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK65" reset. 
2020-12-29 14:40:31.7418|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK65" disconnecting. 
2020-12-29 14:40:31.7418|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK65" sending FIN. 
2020-12-29 14:40:31.7591|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK65" stopped. 
2020-12-29 14:40:31.8153|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/   
2020-12-29 14:40:31.8607|4|DEBUG|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|The request path / does not match a supported file type 
2020-12-29 14:40:32.0160|1|DEBUG|Microsoft.AspNetCore.Routing.RouteBase|Request successfully matched the route with name 'default' and template '{controller=Home}/{action=Index}/{id?}'. 
2020-12-29 14:40:32.1120|1|DEBUG|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executing action ASP.NET_Core_2___VS2017.Controllers.HomeController.Index (ASP.NET Core 2 - VS2017) 
2020-12-29 14:40:32.1326|1|INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executing action method ASP.NET_Core_2___VS2017.Controllers.HomeController.Index (ASP.NET Core 2 - VS2017) with arguments ((null)) - ModelState is Valid 
2020-12-29 14:40:32.1326|0|INFO|ASP.NET_Core_2___VS2017.Controllers.HomeController|Hello, this is the index! 
2020-12-29 14:40:32.1620|2|DEBUG|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executed action method ASP.NET_Core_2___VS2017.Controllers.HomeController.Index (ASP.NET Core 2 - VS2017), returned result Microsoft.AspNetCore.Mvc.ViewResult. 
2020-12-29 14:40:32.1620|1|DEBUG|Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine|View lookup cache miss for view 'Index' in controller 'Home'. 
2020-12-29 14:40:33.6906|1|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\Home\Index.cshtml' started. 
2020-12-29 14:40:35.7180|2|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\Home\Index.cshtml' completed in 2024.1338ms. 
2020-12-29 14:40:35.7988|1|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\_ViewStart.cshtml' started. 
2020-12-29 14:40:35.8637|2|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\_ViewStart.cshtml' completed in 63.9912ms. 
2020-12-29 14:40:35.8710|2|DEBUG|Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor|The view 'Index' was found. 
2020-12-29 14:40:35.8710|1|INFO|Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor|Executing ViewResult, running view at path /Views/Home/Index.cshtml. 
2020-12-29 14:40:35.9577|1|DEBUG|Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine|View lookup cache miss for view '_Layout' in controller 'Home'. 
2020-12-29 14:40:36.0454|1|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\Shared\_Layout.cshtml' started. 
2020-12-29 14:40:36.2080|2|DEBUG|Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler|Compilation of the generated code for the Razor file at 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\Views\Shared\_Layout.cshtml' completed in 161.8031ms. 
2020-12-29 14:40:36.2209|2|DEBUG|Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper|Tag helper component 'Microsoft.AspNetCore.ApplicationInsights.HostingStartup.JavaScriptSnippetTagHelperComponent' initialized. 
2020-12-29 14:40:36.2209|3|DEBUG|Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper|Tag helper component 'Microsoft.AspNetCore.ApplicationInsights.HostingStartup.JavaScriptSnippetTagHelperComponent' processed. 
2020-12-29 14:40:36.2367|2|DEBUG|Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper|Tag helper component 'Microsoft.AspNetCore.ApplicationInsights.HostingStartup.JavaScriptSnippetTagHelperComponent' initialized. 
2020-12-29 14:40:36.2367|3|DEBUG|Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper|Tag helper component 'Microsoft.AspNetCore.ApplicationInsights.HostingStartup.JavaScriptSnippetTagHelperComponent' processed. 
2020-12-29 14:40:36.2942|2|INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executed action ASP.NET_Core_2___VS2017.Controllers.HomeController.Index (ASP.NET Core 2 - VS2017) in 4181.1451ms 
2020-12-29 14:40:36.3036|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK64" completed keep alive response. 
2020-12-29 14:40:36.3273|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 4515.4954ms 200 text/html; charset=utf-8 
2020-12-29 14:40:36.3273|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" started. 
2020-12-29 14:40:36.3273|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" started. 
2020-12-29 14:40:36.3386|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/lib/bootstrap/dist/css/bootstrap.css   
2020-12-29 14:40:36.3386|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/css/site.css   
2020-12-29 14:40:36.3610|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/css/site.css'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\css\site.css' 
2020-12-29 14:40:36.3610|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/lib/bootstrap/dist/css/bootstrap.css'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\lib\bootstrap\dist\css\bootstrap.css' 
2020-12-29 14:40:36.4312|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" completed keep alive response. 
2020-12-29 14:40:36.4312|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 90.8043ms 200 text/css 
2020-12-29 14:40:36.4312|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" completed keep alive response. 
2020-12-29 14:40:36.4312|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 98.4683ms 200 text/css 
2020-12-29 14:40:36.4710|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK68" started. 
2020-12-29 14:40:36.4710|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK69" started. 
2020-12-29 14:40:36.4819|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/lib/jquery/dist/jquery.js   
2020-12-29 14:40:36.4819|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/lib/bootstrap/dist/js/bootstrap.js   
2020-12-29 14:40:36.4819|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/images/banner2.svg   
2020-12-29 14:40:36.4819|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/js/site.js?v=ji3-IxbEzYWjzzLCGkF1KDjrT2jLbbrSYXw-AhMPNIA   
2020-12-29 14:40:36.4819|1|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK6A" started. 
2020-12-29 14:40:36.4819|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/js/site.js'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\js\site.js' 
2020-12-29 14:40:36.4819|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/lib/jquery/dist/jquery.js'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\lib\jquery\dist\jquery.js' 
2020-12-29 14:40:36.4819|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/images/banner2.svg'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\images\banner2.svg' 
2020-12-29 14:40:36.4933|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" completed keep alive response. 
2020-12-29 14:40:36.4819|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/lib/bootstrap/dist/js/bootstrap.js'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\lib\bootstrap\dist\js\bootstrap.js' 
2020-12-29 14:40:36.4933|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 20.2541ms 200 application/javascript 
2020-12-29 14:40:36.5143|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" completed keep alive response. 
2020-12-29 14:40:36.5143|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 32.361ms 200 image/svg+xml 
2020-12-29 14:40:36.5143|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2   
2020-12-29 14:40:36.5401|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\lib\bootstrap\dist\fonts\glyphicons-halflings-regular.woff2' 
2020-12-29 14:40:36.5401|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/images/banner1.svg   
2020-12-29 14:40:36.5401|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/images/banner1.svg'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\images\banner1.svg' 
2020-12-29 14:40:36.5539|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" completed keep alive response. 
2020-12-29 14:40:36.5539|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 39.9074ms 200 font/woff2 
2020-12-29 14:40:36.5745|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/images/banner3.svg   
2020-12-29 14:40:36.5745|1|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:56152/images/banner4.svg   
2020-12-29 14:40:36.5951|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK68" completed keep alive response. 
2020-12-29 14:40:36.6015|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 119.5389ms 200 application/javascript 
2020-12-29 14:40:36.6015|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/images/banner4.svg'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\images\banner4.svg' 
2020-12-29 14:40:36.5745|2|INFO|Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware|Sending file. Request path: '/images/banner3.svg'. Physical path: 'X:\nlog\NLog.Web\examples\ASP.NET Core 2\Visual Studio 2017\ASP.NET Core 2 - VS2017\wwwroot\images\banner3.svg' 
2020-12-29 14:40:36.6946|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK64" completed keep alive response. 
2020-12-29 14:40:36.6703|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" completed keep alive response. 
2020-12-29 14:40:36.6946|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 119.7561ms 200 image/svg+xml 
2020-12-29 14:40:36.6015|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK69" completed keep alive response. 
2020-12-29 14:40:36.7137|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 170.2078ms 200 image/svg+xml 
2020-12-29 14:40:36.7137|9|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK6A" completed keep alive response. 
2020-12-29 14:40:36.7560|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 181.4017ms 200 image/svg+xml 
2020-12-29 14:40:36.6946|2|INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 216.2838ms 200 application/javascript 
2020-12-29 14:42:21.6657|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK68" received FIN. 
2020-12-29 14:42:21.6657|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK67" received FIN. 
2020-12-29 14:42:21.6657|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" disconnecting. 
2020-12-29 14:42:21.6657|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK69" received FIN. 
2020-12-29 14:42:21.6657|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK69" disconnecting. 
2020-12-29 14:42:21.6657|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK67" sending FIN. 
2020-12-29 14:42:21.6657|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK68" disconnecting. 
2020-12-29 14:42:21.6657|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK69" stopped. 
2020-12-29 14:42:21.6800|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK66" received FIN. 
2020-12-29 14:42:21.6800|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" disconnecting. 
2020-12-29 14:42:21.6657|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK69" sending FIN. 
2020-12-29 14:42:21.6657|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK67" stopped. 
2020-12-29 14:42:21.6800|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK6A" received FIN. 
2020-12-29 14:42:21.6800|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK6A" disconnecting. 
2020-12-29 14:42:21.6800|6|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK64" received FIN. 
2020-12-29 14:42:21.6800|10|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK64" disconnecting. 
2020-12-29 14:42:21.6800|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK68" sending FIN. 
2020-12-29 14:42:21.6800|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK68" stopped. 
2020-12-29 14:42:21.6800|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK66" sending FIN. 
2020-12-29 14:42:21.6800|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK66" stopped. 
2020-12-29 14:42:21.6943|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK6A" stopped. 
2020-12-29 14:42:21.6943|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK6A" sending FIN. 
2020-12-29 14:42:21.6943|7|DEBUG|Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv|Connection id "0HL8G4U42CK64" sending FIN. 
2020-12-29 14:42:21.6943|2|DEBUG|Microsoft.AspNetCore.Server.Kestrel|Connection id "0HL8G4U42CK64" stopped. 

The file format can be defined using the layout attribute in a target element.

NLog is easy to use and configure with ASP.NET Core application. In this post, I have explained about NLog – file logging configuration with ASP.NET Core 2.