Enable access to APIs

PowerServer Management APIs can be used to get and modify information about the license, database connection, transaction etc., therefore, access to PowerServer Management APIs are disabled by default, in order to protect these sensitive information. A 403 "Forbidden" error or 405 "Method Not Allowed" error will occur when the APIs are requested.

To enable the use of the APIs, go to UserExtensions project -> UserStartup.cs, uncomment the following script if it is commented or add the script if it does not exist:

app.UsePowerServerManagementAPI()


After the APIs are enabled, you can access the APIs now.

If you want to have finer grained control over the APIs, you can define the authentication and authorization service.

You can use the built-in authentication service or other third-party authentication service.

To use the built-in authentication service, you can follow instructions in Authentication, and then define the authorization logics in UserExtensions project -> Authentication -> AuthenticationExtensions.cs:


The following uses JWT as an example to show you how to define the authentication and authorization service.

Step 1: Enable the use of the APIs.

Go to UserExtensions project -> UserStartup.cs, uncomment the following script if it is commented or add the script if it does not exist).

app.UsePowerServerManagementAPI()

Step 2: Use the built-in JWT server to implement authenticate to the installable cloud app.

Without authentication, anyone can access your application and the APIs without restriction.

To ensure only verified users can access the PowerServer Web APIs and PowerServer Management APIs, you can follow instructions in Authentication > Using JWT to implement basic authentication.

Step 3: Add authorization on top of authentication.

Once the user is authenticated, you can determine only users with specific roles (instead of all users) are allowed to access the PowerServer Management API.

You can add a few lines of code to implement the policy-based authorization in the authentication template.

Take the JWT authentication template as an example.

a) Add a role to the user information in the JWT token.

For example: In the DefaultUserStore.cs file, you can specify the role for user Alice as "Normal" and the role for user Bob as "Admin". The JWT token generated in this way will carry the role information.

using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using IdentityModel;
using Microsoft.Extensions.Logging;

namespace UserExtensions
{
    public class DefaultUserStore : IUserStore
    {
        private readonly ILogger _logger;
        private readonly List<UserModel> _users;

        public DefaultUserStore(ILogger<DefaultUserStore> logger)
        {
            _logger = logger;

            _users = new List<UserModel>
            {
                new UserModel
                {
                    Username = "alice",
                    Password = "alice",
                    Claims = new[]
                    {
                        // Claims the "serverapi" scope
                        new Claim(JwtClaimTypes.Scope, "serverapi"),
                        new Claim(JwtClaimTypes.Name, "Alice Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Alice"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.WebSite, "http://alice.com"),
                        new Claim(JwtClaimTypes.Email, "AliceSmith@email.com"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.Role, "Normal")
                    }
                },
                new UserModel
                {
                    Username = "bob",
                    Password = "bob",
                    Claims = new[]
                    {
                        //  Claims the "serverapi" scope
                        new Claim(JwtClaimTypes.Scope, "serverapi"),
                        new Claim(JwtClaimTypes.Name, "Bob Smith"),
                        new Claim(JwtClaimTypes.GivenName, "Bob"),
                        new Claim(JwtClaimTypes.FamilyName, "Smith"),
                        new Claim(JwtClaimTypes.WebSite, "http://bob.com"),
                        new Claim(JwtClaimTypes.Email, "BobSmith@email.com"),
                        new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
                        new Claim(JwtClaimTypes.Role, "Admin")
                    }
                }
            };
        }

        public UserModel? ValidateCredentials(string username, string password)
       {
            // Logs in with the default user in the script
            // If you want to integrate an existing user management system (such as database), uses DatabaseUserStore instead
            var user = _users.FirstOrDefault(x => x.Username == username && x.Password == password);

            if (user != null)
            {
                // Logs the authentication-success information
                _logger.LogInformation($"User <{username}> logged in.");

                return user;
            }
            else
            {
                // Logs the authentication-failed information
                _logger.LogError($"Invalid login attempt.");

                return default;
            }
        }
    }
}

b) Modify the authorization policy to check the user role in the JWT token.

For example: You can add the rule for PowerServerConstants.ManagementAPIAuthorizePolicy in AuthenticationExtensions.cs.

Add the code policy.RequireRole("Admin"); to ensure that the PowerServer Management APIs can only be accessed by the user whose role is "Admin". Other users will not be able to access these APIs.

Note: You need to modify the code block under the comment "// Rewrite the PowerServer management API authorization policy". (as you want only users with specific roles to access the PowerServer Management APIs.)

You should NOT modify the code block under the comment "// Rewrite the default PowerServer authorization policy". (otherwise, users of all roles can still access the normal PowerServer Web APIs.)

using System;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
using PowerServer.Core;

namespace UserExtensions
{
    public static class AuthenticationExtensions
    {
        // To be called in Startup.ConfigureServices, for adding services related with the authentication module
        public static IServiceCollection AddPowerServerAuthentication(
            this IServiceCollection services, IConfiguration configuration)
        {
            // Uses JWT Server as the built-in authentication server
            // Injects the JWT request handling program, user storage and token handler

            // Adds the user validator to validate the user login
            // Adds the token generator
            services.AddSingleton<TokenHandler>();

            // Configures user data
            // The script below injects a default implementation that directly reads user data from code for validation
            services.AddSingleton<IUserStore, DefaultUserStore>();

            // Uncomment the following script if you want to integrate an existing user management system (like a database)
            // Note: You shall use DatabaseUserStore to implement the logics of getting users from the database
            //services.AddSingleton<IUserStore, DatabaseUserStore>();

            // Injects the JWT Server middleware into the request pipeline
            services.AddTransient<IStartupFilter, AuthenticationMiddleware>();

            // Adds the PowerServer authentication configuration
            // see https://docs.microsoft.com/en-us/aspnet/core/security/authentication
            services.AddAuthentication()
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                {
                    // Loads the authentication configuration from Authentication.json

                    // Loads the token issuer
                    var issuer = configuration["JwtSetting:Issuer"];

                    // Loads the token audience 
                    var audience = configuration["JwtSetting:Audience"];

                    // Loads the token key
                    var securityKey = configuration["JwtSetting:SecurityKey"];

                    SymmetricSecurityKey? symmetricKey = null;

                    if (!String.IsNullOrWhiteSpace(securityKey))
                    {
                        symmetricKey = new SymmetricSecurityKey(Convert.FromBase64String(securityKey));
                    }

                    // Sets the token validation parameters 
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        // Sets the token issuer 
                        ValidIssuer = issuer,
                        // Enables the validation of the token issuer 
                        ValidateIssuer = true,

                        // Sets the token audience 
                        ValidAudience = audience,
                        // Enables the validation of token audience 
                        ValidateAudience = true,

                        // Sets the token key 
                        IssuerSigningKey = symmetricKey,
                        // Enables the validation of token key 
                        ValidateIssuerSigningKey = true,

                        // Sets the token expiration time 
                        RequireExpirationTime = true,
                        // Enables the validation of token expiration time 
                        ValidateLifetime = true,
                    };

                    options.IncludeErrorDetails = true;

                    //  It is recommended to enable this option in the development environment, bt disable it in production for GDPR compliance
                    // see https://aka.ms/IdentityModel/PII
                    IdentityModelEventSource.ShowPII = true;
                });

            // Adds the PowerServer authorization configuration
            // see https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction
            services.AddAuthorization(options =>
            {
                // Rewrite the default PowerServer authorization policy
                options.AddPolicy(PowerServerConstants.DefaultAuthorizePolicy, policy =>
                {
                    // Requires that the authenciation is successful
                    policy.RequireAuthenticatedUser();

                    // Requires that the JwtBearer method is used
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);

                    // Requires that access_token contains "serverapi" scope
                    policy.RequireClaim("scope", "serverapi");

                    //  You can define additional authentication logics here
                    // ...
                });

                // Rewrite the PowerServer management API authorization policy
                options.AddPolicy(PowerServerConstants.ManagementAPIAuthorizePolicy, policy =>
                {
                    // Requires that the authenciation is successful
                    policy.RequireAuthenticatedUser();

                    // Requires that the JwtBearer method is used
                    policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);

                    // Requires that access_token contains "serverapi" scope
                    policy.RequireClaim("scope", "serverapi");
                    
                    //  You can define additional authentication logics here
                    policy.RequireRole("Admin");
                });
            });

            return services;
        }
    }
}