<file_sep>﻿using Duende.IdentityServer.Models;
using Duende.IdentityServer.Test;
using System;
using System.Collections.Generic;
using System.Security.Claims;
using static Duende.IdentityServer.IdentityServerConstants;

namespace WebApplication1
{
    public class Config
    {
        public static IEnumerable<IdentityResource> IdentityResources =>
            new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email()
            };

        public static IEnumerable<ApiScope> ApiScopes =>
            new List<ApiScope>
            {
                 new ApiScope("api.hih", "HIH API"),
                 new ApiScope("api.acgallery", "Gallery API"),
                 new ApiScope("api.knowledgebuilder", "Knowledge Builder API")
            };

#if DEBUG
        //public static List<TestUser> TestUsers =>
        //    new()
        //    {
        //        new TestUser()
        //        {
        //            SubjectId = "C731D080-1B8A-4152-833A-431FCD099C01",
        //            Username = "Admin",
        //            Password = "<PASSWORD>"
        //        }
        //    };
#endif

        public static IEnumerable<Client> Clients => new List<Client>
            {
                new Client
                {
                    ClientName = "AC HIH App",
                    ClientId = "achihui.js",
                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,

                    AllowAccessTokensViaBrowser = true,
                    AllowOfflineAccess = true, // For refresh token

                    RequireConsent = false,
                    AlwaysIncludeUserClaimsInIdToken = true,

                    RedirectUris = new List<String>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/hih"
#else
                        "http://localhost:29521",
                        "https://localhost:29521"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    PostLogoutRedirectUris = new List<string>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/hih"
#else
                        "http://localhost:29521",
                        "https://localhost:29521"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    AllowedScopes = new List<String>
                    {
                        StandardScopes.OpenId,
                        StandardScopes.Profile,
                        StandardScopes.Email,
                        StandardScopes.OfflineAccess,
                        "api.hih"
                    },
                    AccessTokenLifetime = 3600
                },
                new Client
                {
                    ClientName = "<NAME>",
                    ClientId = "acgallery.app",
                    //AllowedGrantTypes = GrantTypes.Implicit,
                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,

                    AllowAccessTokensViaBrowser = true,
                    AllowOfflineAccess = true, // For refresh token

                    AlwaysIncludeUserClaimsInIdToken = true,
                    RequireConsent = false,
                    RedirectUris = new List<String>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/gallery"
#else
                        "https://localhost:16001"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    PostLogoutRedirectUris = new List<string>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/gallery"
#else
                        "https://localhost:16001"
                        // "http://localhost:16001"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    AllowedScopes = new List<string>
                    {
                        StandardScopes.OpenId,
                        StandardScopes.Profile,
                        StandardScopes.OfflineAccess,
                        StandardScopes.Email,
                        "api.acgallery"
                    },
                    AccessTokenLifetime = 3600
                },
                new Client
                {
                    ClientName = "<NAME>",
                    ClientId = "knowledgebuilder.js",
                    //AllowedGrantTypes = GrantTypes.Implicit,
                    AllowedGrantTypes = GrantTypes.Code,
                    RequireClientSecret = false,
                    RequirePkce = true,

                    AllowAccessTokensViaBrowser = true,
                    AllowOfflineAccess = true, // For refresh token
                    AlwaysIncludeUserClaimsInIdToken = true,
                    RequireConsent = false,
                    RedirectUris = new List<String>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/math"
#else
                        "http://localhost:44367",
                        "https://localhost:44367"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    PostLogoutRedirectUris = new List<string>
                    {
#if DEBUG
#if USE_ALIYUN
                        "https://www.alvachien.com/math"
#else
                        "http://localhost:44367",
                        "https://localhost:44367"
#endif
#else
                        "https://www.alvachien.com"
#endif
                    },
                    AllowedScopes = new List<String>
                    {
                        StandardScopes.OpenId,
                        StandardScopes.Profile,
                        StandardScopes.Email,
                        StandardScopes.OfflineAccess,
                        "api.knowledgebuilder"
                   },
                    AccessTokenLifetime = 3600
#if DEBUG
                },
                new Client
                {
                    ClientId = "Postman",
                    ClientName = "<NAME>",
                    // AllowAccessTokensViaBrowser = true,
                    RequireConsent = false,
                    RedirectUris = { "https://www.getpostman.com/oauth2/callback" },
                    // RedirectUris = { "https://oauth.pstmn.io/v1/callback" },
                    PostLogoutRedirectUris = { "https://www.getpostman.com" },
                    AllowedCorsOrigins = { "https://www.getpostman.com" },
                    EnableLocalLogin = true,

                    AllowedGrantTypes = new []
                    {
                        GrantType.ResourceOwnerPassword
                    },
                    ClientSecrets = { new Secret("Postman".Sha256()) },
                    ClientUri = null,
                    AllowOfflineAccess = true,
                    Enabled = true,
                    AllowedScopes = new []
                    {
                        StandardScopes.OpenId,
                        StandardScopes.Profile,
                        StandardScopes.Email,
                        // StandardScopes.OfflineAccess,
                        "api.acquiz",
                        "api.acgallery",
                        "api.hih",
                        "api.knowledge"
                    }
#endif
                }
        };        
    }
}
<file_sep># acidserver
Server for identities which cross web apps, built on IdentityServer


## appsettings.json
The file appsettings.json has been removed.

An example file as following:

{

  "ConnectionStrings": {
  
    "DefaultConnection": "Data Source=XXX;Initial Catalog=XXX;Persist Security Info=True;User ID=XXX;Password=XXX;",
	
    "DebugConnection": "Server=XXX;Database=XXX;Integrated Security=SSPI;MultipleActiveResultSets=true"
	
  },
  
  "Logging": {
  
    "IncludeScopes": false,
	
    "LogLevel": {
	
      "Default": "Debug",
	  
      "System": "Information",
	  
      "Microsoft": "Information"
	  
    }
	
  }
  
}

<file_sep>using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Data;

namespace WebApplication1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
#if DEBUG
#if USE_ALIYUN
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("AliyunConnection")));
#else
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
#endif
            services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("DefaultConnection")));
#endif
            services.AddDatabaseDeveloperPageExceptionFilter();

            services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
                .AddEntityFrameworkStores<ApplicationDbContext>();
            services.AddControllersWithViews();

            services.AddCors();

            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://docs.duendesoftware.com/identityserver/v5/fundamentals/resources/
                options.EmitStaticAudienceClaim = true;
            })
                .AddInMemoryIdentityResources(Config.IdentityResources)
                .AddInMemoryApiScopes(Config.ApiScopes)
                .AddInMemoryClients(Config.Clients)
//#if DEBUG
//                .AddTestUsers(Config.TestUsers)
//#endif
                .AddAspNetIdentity<IdentityUser>();

            services.AddAuthentication();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseMigrationsEndPoint();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }
            app.UseHttpsRedirection();

            app.UseCors(option =>
            {
                option.WithOrigins(
#if DEBUG
#if USE_ALIYUN
                    "https://www.alvachien.com/hih", // HIH UI
                    "https://www.alvachien.com/gallery", // Gallery
                    "https://www.alvachien.com/math", // Math exercise
                    "https://www.alvachien.com/hihapi", // HIH API
                    "https://www.alvachien.com/galleryapi", // Gallery API
                    "https://www.alvachien.com/quizapi" // Quiz API
#else
                    "https://localhost:16001", // AC gallery
                    "https://localhost:29521", // AC HIH UI
                    "https://localhost:44366", // AC HIH API
                    "https://localhost:25325", // AC Gallery API
                    // "https://localhost:20000", // Math exercise
                    // "https://localhost:54020"  // AC Quiz API
                    "https://localhost:44367"    // Knowledge builder
#endif
#else
                    "https://www.alvachien.com" 
#endif
                    )
                .AllowAnyHeader()
                .AllowAnyMethod()
                .AllowCredentials();
            });

            app.UseStaticFiles();

            app.UseRouting();

            app.UseIdentityServer();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
                endpoints.MapRazorPages();
            });
        }
    }
}
<file_sep>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Duende.IdentityServer.Services;
using Duende.IdentityServer.Stores;
using Duende.IdentityServer.Events;
using Microsoft.AspNetCore.Authentication;
using Duende.IdentityServer.Extensions;

namespace WebApplication1.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class LogoutModel : PageModel
    {
        private readonly SignInManager<IdentityUser> _signInManager;
        private readonly ILogger<LogoutModel> _logger;
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IIdentityServerInteractionService _interaction;
        private readonly IClientStore _clientStore;
        private readonly IAuthenticationSchemeProvider _schemeProvider;
        private readonly IEventService _events;

        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger,
            UserManager<IdentityUser> userManager,
            IIdentityServerInteractionService interaction,
            IClientStore clientStore,
            IAuthenticationSchemeProvider schemeProvider,
            IEventService events
            )
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
            _interaction = interaction;
            _clientStore = clientStore;
            _schemeProvider = schemeProvider;
            _events = events;
        }

        public void OnGet()
        {
        }

        public async Task<IActionResult> OnPost(string returnUrl = null)
        {
            #region Generated codes by Visual Studio
            //await _signInManager.SignOutAsync();
            //_logger.LogInformation("User logged out.");
            //if (returnUrl != null)
            //{
            //    return LocalRedirect(returnUrl);
            //}
            //else
            //{
            //    return RedirectToPage();
            //}
            #endregion

            if (User?.Identity.IsAuthenticated == true)
            {
                // delete local authentication cookie
                await _signInManager.SignOutAsync();
                _logger.LogInformation("User logged out.");

                // raise the logout event
                await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
            }

            // check if we need to trigger sign-out at an upstream identity provider
            //if (vm.TriggerExternalSignout)
            //{
            //    // build a return URL so the upstream provider will redirect back
            //    // to us after the user has logged out. this allows us to then
            //    // complete our single sign-out processing.
            //    string url = Url.Action("Logout", new { logoutId = vm.LogoutId });

            //    // this triggers a redirect to the external provider for sign-out
            //    return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme);
            //}

            if (returnUrl != null)
            {
                return LocalRedirect(returnUrl);
            }
            else
            {
                return RedirectToPage();
            }
        }
    }
}
