Skip to content

Boilerplate project for MediatR powered C# & Angular Web Project using .NET Core best practices and Auth0

License

Notifications You must be signed in to change notification settings

CodeSwifterGit/CodeSwifterStarter-CSharp-Angular-Auth0

Repository files navigation

CodeSwifterStarter-CSharp-Angular-Auth0

This is the boilerplate repository for MediatR powered C# Angular Web Project using .NET Core best practices and Auth0 authorization provider.

Installing

The repository is a prerequisite for an application generated by the CodeSwifter application generator, which uses it to create a new project when you run the code generation wizard from your CodeSwifter repository to which you previously imported the ANGULAR SPA .NET CORE TEMPLATE WITH AUTH0 PROVIDER template bundle.

Running the application

Once the CodeSwifter application generates the code using the database structure, that you've previously defined, or imported, and merges it with this repository, you will get two applications created. The backend (Web.Api) and the frontend (Angular).

Backend (Web.Api application)

The Web.Api application can be started using Visual Studio (F5), or by running the appropriate bash file in the Web.Api project directory as follows:

  • run-{environment_name}.bat (on Windows)
  • run-{environment_name}.sh (on Linux or Mac) - don't forget to run chmod +x run.sh to make bash script executable

The Frontend (Angular)

  1. Navigate to the CodeSwifterStarter.Web.Api/ClientApp directory and run npm install (make sure to install Node.js first)
  2. In the same directory, run ng serve --ssl
  3. Open the following link in the browser https://localhost:4200

Note that the frontend proxies communication to port 6220, as configured in proxy.conf.json.

Changing The Configuration

Backend

In the Web.Api project, there are separate appsetings.json files for the local, staging, production, and development environment in the Web.Api project. You should adjust the values to your database and other configuration settings. For now, skip the SecurityProvider section, because you will be editing it after the Auth0 setup is complete.

Note that there is a separate set of configuration files in the Application.Test project as well.

The src/CodeSwifterStarter.Common/Models/ServerConfiguration.cs class defines the expected structure of appsettings.json. Modify it to fit your needs.

The src/CodeSwifterStarter.Web.Api/Helpers/ContextConfiguration.cs class defines the loading type and SQL provider which application uses. Modify it to fit your needs.

For example, if you don't want to use lazy loading, please remove the UseLazyLoadingProxies() line, but be sure to create a separate partial class of type QueryManager class for each entity, override the GetQueryWithIncludes function, and return a query with all includes needed to retrieve dependent data.

Example ProductQueryManager.Custom.cs

public override IQueryable<Entities.Product> GetQueryWithIncludes(IQueryable<Entities.Product> queryable)
        {
            return queryable
                .Include(pv => pv.ProductVariants)
                .ThenInclude(v => v.Variant)
                .Include(po => po.ProductOrders)
                .ThenInclude(o => o.Order);
        }

Frontend

Auth0 - Third Party Authorization Provider
To configure the Auth0 provider for authorization, go to the Auth web site and perform the following steps:
  • Open an account at Auth0
  • Keep in mind that you will need at least 2 tenants. One for production and the other for dev, staging and local environment. The steps, explained in the following chapter need to be executed for each tenant.

SINGLE PAGE APP

  • In the left navigation pane select Applications, Applications and remove the Default App, bacause we don't need it
  • Next click on CREATE APPLICATION and select "Single Page Web Applications"
  • Give it the name "CHANGE_ME Login", where CHANGE_ME is the name that you want your users to see in the login dialog.
  • Select Angular as technology.
  • Click the Settings tab.
  • Use the configuration below to populate the settings.

For production and staging environment tenant:

Allowed Callback URLs

https://codeswifterstarter.com

Allowed Logout URLs

https://codeswifterstarter.com/website/user/logout

Allowed Web Origins

https://codeswifterstarter.com, https://codeswifterstarter.eu.auth0.com

Allowed Origins (CORS)

https://codeswifterstarter.com

For dev, staging and local environment tenant:

Allowed Callback URLs

https://localhost:4200/website/user/authorize, https://localhost:6220/website/user/authorize, https://staging.codeswifterstarter.com/website/user/authorize

Allowed Logout URLs

https://localhost:4200, https://localhost:6220, https://staging.codeswifterstarter.com

Allowed Web Origins

https://localhost:4200, https://localhost:6220, https://staging.codeswifterstarter.com, https://codeswifterstarter-dev.eu.auth0.com

Allowed Origins (CORS)

http://localhost:4200, https://localhost:6220, https://staging.codeswifterstarter.com

MACHINE TO MACHINE API

(required for your backend to communicate with Auth0 and to assign initial roles to new users)
  • Click Applications in the left pane and then CREATE APPLICATION
  • Give the name to your application "CHANGE_ME Backend", where CHANGE_ME is the name that you want your users to see in the login dialog.
  • Select "Machine to Machine applications" and click CREATE
  • Select Auth0 Management API in the drop-down list.
  • Make sure to select the following permissions:
read:users
update:users

read:users_app_metadata
update:users_app_metadata
delete:users_app_metadata
create:users_app_metadata

read:roles
create:roles
delete:roles
update:roles

create:role_members
read:role_members
delete:role_members
  • Once you are finished with the permissions selection, click AUTHORIZE

  • In the left navigation pane click API

  • Select Auth0 Management API

  • Click on API Explorer

  • Click on CREATE & AUTHORIZE TEST APPLICATION

  • On step "Authorize Machine to Machine Application

Make sure to update src/CodeSwifterStarter.Web.Api/ClientApp/auth.config.json with credentials from the created Auth0 application.

Configuring Rules for New Users

In order to add initial roles for each user that creates an account (on Auth0 when accessing your application), please add all security requirements as roles in Auth0 application. Next, navigate to Auth Pipeline - Rules and add new rule named Add default roles. Next edit the rule and paste the following code (make sure to update the values based on the comments in the script):

function setRolesToUser(user, context, callback) {

  // Roles should only be set to verified users.
  /*  if (!user.email || !user.email_verified) {
      return callback(null, user, context);
    }*/

  // Add base claims
  context.accessToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'] = user.name;
  context.accessToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] = user.email;
  context.accessToken['https://codeswifterstarter.com/identity/claims/email_verified'] = user.email_verified;
  context.accessToken['https://codeswifterstarter.com/identity/claims/picture'] = user.picture;
  context.accessToken['https://codeswifterstarter.com/identity/claims/nickname'] = user.nickname;
  context.accessToken['https://codeswifterstarter.com/identity/claims/created_at'] = user.created_at;
  context.accessToken['https://codeswifterstarter.com/identity/claims/updated_at'] = user.updated_at;

  // Using ManagementClient in order to add initial role for a new user
  const ManagementClient = require('auth0@2.27.0').ManagementClient;
  const management = new ManagementClient({
    domain: auth0.domain,
    clientId: 'MACHINE2MACHINE_APP_CLIENT_ID', // Put Client ID of your Auth0 Machine2Machine Application (in the left navigation pane of Auth0 select Applications, Applications and find your backend app)
  	clientSecret: 'MACHINE2MACHINE_APP_SECRET', // Put secret of your Auth0 Machine2Machine application
    scope: "read:users update:users read:roles create:role_members read:role_members",
    audience: 'https://codeswifter-dev.eu.auth0.com/api/v2/', // Put the audience of your management app here
    tokenProvider: {
      enableCache: true,
      cacheTTLInSeconds: 10
    }
  });

  const mustHaveRoles = ['ReadNonSensitiveData', 'WriteNonSensitiveData']; // PROVIDE LIST OF YOUR SECURITY REQUIREMENTS HERE
  context.authorization = context.authorization || {};
  context.authorization.roles = context.authorization.roles || [];  
  const missingRoles = mustHaveRoles.filter(m => !context.authorization.roles.find(r => r === m));

  if (missingRoles.length > 0) {
    management.getRoles({ per_page: 100, page: 0 }, function (err, roles) {
      if (!err) {
        const roleObjectsToAdd = roles.filter(r => missingRoles.find(m => m === r.name));

        if (!!roleObjectsToAdd && roleObjectsToAdd.length === missingRoles.length) {
          console.log(roleObjectsToAdd);
          const mustHaveRoleObjectsIds = roleObjectsToAdd.map(m => m.id);
          management.users.assignRoles({ id : user.user_id}, { 'roles': mustHaveRoleObjectsIds }, function (err, user) {
            if (!err) {
              missingRoles.forEach(r => { context.authorization.roles.push(r); });
              context.accessToken['https://codeswifterstarter.com/identity/claims/permissions'] = context.authorization.roles.join(' ');
            } else {
              console.log(err);
            }

            callback(null, user, context);
            return;
          });
        } else {
          console.log(`Some roles, that user needs to be associated with, cannot be found.`);
          callback(null, user, context);
          return;
        }
      } else {
        console.log(err);
        callback(null, user, context);
        return;
      }
    });
  } else {
    context.accessToken['https://codeswifterstarter.com/identity/claims/permissions'] = context.authorization.roles.join(' ');
    callback(null, user, context);
  }
}
Writing Configuration settings to Your backend and frontend code settings
Frontend
  • Edit /src/CodeSwifterStarter.Web.Api/ClientApp/auth.config.json and for each environment (dev is used for local, dev and staging) put information as follows
    // Client ID and domain information can be found in the settings section of your Auth0 frontend application
      "clientId": "AUTH0_FRONTEND_APP_CLIENT_ID",
      "domain": "YOUR_AUTH0_TENANT_DOMAIN",
      "audience": "https://YOUR_AUTH0_TENANT_DOMAIN/api/v2/"
Backend
  • Edit /src/CodeSwifter.Web.Api/appsettings.json and /src/CodeSwifter.Web.Api/appsettings.Production.json in order to populate the following section
"SecurityProvider": {
    "Authority": "https://YOUR_AUTH0_TENANT_DOMAIN/",
    "Audience": "https://YOUR_AUTH0_TENANT_DOMAIN/api/v2/",
    "ManagementApi": {
      "ClientId": "AUTH0_BACKEND_APP_CLIENT_ID",
      "ClientSecret": "AUTH0_BACKEND_APP_SECRET"
    }
  }
Other Frontend Configuration Settings

Please configure src/CodeSwifterStarter.Web.Api/ClientApp/src/environments/environment.ts and environment.prod.ts to meet your expectations.

Running Migrations

Please make sure to replace \ with / for Linux or Mac environment.

Running Migrations from Package Manager Console (Visual Studio only)

Open Package Manager Console in Visual Studio and select CodeSwifterStarter.Persistance as a default branch !!!

Adding a Migration
Add-Migration -Name InitialCreate -Context CodeSwifterStarterDbContext -StartupProject CodeSwifterStarter.Web.Api -OutputDir Migrations\
Updating the Database
Update-Database -Context CodeSwifterStarterDbContext -StartupProject CodeSwifterStarter.Web.Api
Removing Last Migration
remove-migration -force -Context CodeSwifterStarterDbContext -StartupProject CodeSwifterStarter.Web.Api

Running Migrations from Terminal

Adding a Migration
dotnet ef migrations add InitialCreate --context CodeSwifterStarterDbContext --startup-project CodeSwifterStarter.Web.Api --project CodeSwifterStarter.Persistence -o Migrations\ -- --environment Local
Updating the Database
dotnet ef database update --context CodeSwifterStarterDbContext --startup-project CodeSwifterStarter.Web.Api --project CodeSwifterStarter.Persistence -- --environment Local
Removing Last Migration
dotnet ef migrations remove --force --context CodeSwifterStarterDbContext --startup-project CodeSwifterStarter.Web.Api --project CodeSwifterStarter.Persistence -- --environment Local

Serilog Logging

Running Seq in Local environment

Note that docker can be used free of charge for single developer use in production environment too.

Run the server
docker run --rm -it -e ACCEPT_EULA=Y -p 5341:80 datalust/seq
Browse the log

For more information how to configure the logging, visit the following link.

Install SEQ in Production (or Staging, and DEV) environment

For information how to install SEQ in production environment visit the following link.

Configuring Serilog

More information on configuring Serilog can be found here.

If you only need search-oriented logging system, you can use Elastic Search + Kibana. For alert oriented purposes, we suggest SEQ.

Swagger Support

You can get swagger.json specification (currently set up for development or local environment only) by visiting https://localhost:6220/swagger/v1/swagger.json.

To explore API specification, you can access Swagger UI by visiting https://localhost:6220/swagger

Formatting Support

There are formatting rules configured for C#, Typescript and JSON code.

For C# code (Visual Studio) it would be great if you have Resharper installed, as this repository already contains settings for Resharper that are applied when formatting C# code. In order to format the particular directory with subdirectories, right click the directory and select Cleanup Code.

For Typescript and JSON (VS Code), it would be great if you have Format Files extension installed, along with Prettier, as all workspace settings (found in /src/CodeSwifterStarter.Web.Api/ClientApp/.vscode/settings.json) are configured to easily perform reformatting of the generated code, so that the formatting is more consistent accross the team. In order to format the particular directory with subdirectories (with above extensions installed), right click the directory and select Start format files.

Docker Support

The Web.Api project has a Dockerfile included, that you can adjust to fit your needs. Also, Application.Tests project has a Dockerfile included, that shows how tests can be run in an isolated environment on Jenkins.

Jenkins Support

In the root of the repository, you can find Jenkinsfile that is already set up to perform the build on Jenkins, push generated docker images to your docker repository and deploy the application to Kubernetes cluster.

You will probably want to make some changes in Jenkinsfile, especially in the environment section where you need to set the values of the environment variables to match your infrastructure.

In order to validate changes applied to Jenkins file, there is an extension for VS Code called Jenkins Pipeline Linter Connector. In the workspace settings (found in /src/CodeSwifterStarter.Web.Api/ClientApp/.vscode/settings.json), you can set up Linter Connector to use your credentials.

Once that is set up and extension installed, restart VS Code.

Whenever you have Jenkinsfile open, press CTRL+SHIFT+P and type Validate Jenkinsfile and press ENTER. If everything is set up correctly, you should get validation result for your Jenkinsfile.

Kubernates Support

There are two files, one for each environment (a good approach is to configure staging environment, so it looks close to production).

  • production yaml is deploy-production.yaml
  • staging yaml is deploy-staging.yaml

Once you install kubectl (see instructions below), you need to run:

  • kubectl apply -f deploy-production.yaml or
  • kubectl apply -f deploy-staging.yaml

This needs to be done once only. Any subsequent upgrade will be done as last step of Jenkins pipeline (check Jenkinsfile for more information).

Installing Kubernetes CLI on Your Build Server

Please install kubectl CLI tool first. If you don't know how, check these instructions.

Next, download the configuration of your kubernetes cluster from your kubernetes cloud provider and save it somewhere (example: /usr/share/jenkins/.kube/). Open Jenkins Credentials section and add secret file credential pointing to the file that you just downloaded. Adjust environment section of your Jenkinsfile to match added credentials.

Targeting a specific set of nodes

  • Label your nodes with the following labels, so that this script knows which nodes to use for deployment. This needs to be done for production, and staging environment separately.
  kubectl label nodes NODE_NAME1 app=codeswifterstarter-production
  kubectl label nodes NODE_NAME2 app=codeswifterstarter-production
  ...

Using Private Docker Repository

In order to be able to connect to a private docker repository, you will need to create a secret, using the instructions provided here. We suggest to use Create a Secret by providing credentials on the command line approach.

If you will be using a public image instead (we don't think you will, though), remove the following code from your yaml file.

  imagePullSecrets:
  - name: regcred

Digital Ocean Notes:

  • We recommend removing 443 port from your Dockerfile and leaving port 80 only (don't worry, you'll still be able to encrypt the traffic, and get all headers forwarded).
  • If you've created a load balancer, configure it to use SSL Termination. In other words, it will decrypt SSL traffic using the certificate and forward it to your Kubernetes cluster's node(s) unencrypted through the private network. (Sidenote: Since the traffic from the load-balancer to your nodes is happening inside your private network, it doesn't matter if it is unencrypted). Make sure to redirect port 443 to 30080 (listed as nodePort in the yaml file, or change it to fit your needs). Also make sure to change the health port in your load balancer, so it can check health of your nodes periodically.
  • In the zone editor (on Digital Ocean the navigate to Networking, Domains, select your domain), add A record for your domain, and let it point to your load-balancer.

Customizing CRUD operations using CodeSwifter Editor

There are five customization methods, not mutually excluded.

  1. You can use Property Scope do determine where property will be rendered. The options are:
  • Base Domain Class (where all properties which generate database column should be put, also this class is used for creating records)
  • Domain Class (where navigation properties will be rendered)
  • Lookup Class (should be selected if property will be sent from backend to frontend)
  • Update Class (should be selected if property will be sent from frontend to backend as part of the update)
  1. If Property Scope is set up not to render properties into Base Domain Class nor Domain Class, you can tick "Do not generate database column" and provide a getter expression, which should be defined as an expression, the same way you would write it in your backend class. For example "Quantity * Price * (1 + Vat/100)".
  2. All classes are rendered as partial, so by creating a separate file, you can put custom code there. I suggest using naming convention ClassName.Custom.cs.
  3. DynamicQueryManager is the base class for all inherited QueryManagers (for example ContactQueryManager), and it contains a number of virtual methods/functions which can be overriden. I've created a commit in our sample repository which you can find at https://github.com/CodeSwifterGit/adwenture-works/commit/201db0618b56dea3421510b0bcde8ae34e307ca2
  4. CodeSwifter allows you to import templates. Once imported, you can modify them however you want. For me, that is the preferred method. For example, if you want to add some dependency injection into query managers, you would change template named EntityQueryManager.
  5. Lastly, if you need even more customization, you can fork the boilerplate repository, change things there, amend your templates accordingly, update boilerplate connection in your CodeSwifter project (https://codeswifter.com/website/account/repos) and regenerate the code.

Boilerplate Project Support

If you find a bug, or want to request a feature that can be beneficial for other users too, please create an issue in this repository, or fork this repository and create a pull request. If you need more changes, you can fork this repository (or clone it, remove .git subdirectory and push it to your private repository) and adjust it to fit your needs. In that case, make sure to update the boilerplate connection in your repository configuration in the CodeSwifter App.

Thank you

About

Boilerplate project for MediatR powered C# & Angular Web Project using .NET Core best practices and Auth0

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published