{"id":13430551,"url":"https://github.com/arjunkrishna/Blazor-WASM-Identity-gRPC-Alexa","last_synced_at":"2025-03-16T06:30:22.962Z","repository":{"id":37863140,"uuid":"323391108","full_name":"arjunkrishna/Blazor-WASM-Identity-gRPC-Alexa","owner":"arjunkrishna","description":"Blazor WASM, IdentityServer4, Kestrel Web Server, Entity Framework Code First SQLite Database with Multiple Roles, Additional User Claims, gRPC with Roles Authorization, \u0026 Alexa Skill Integration","archived":false,"fork":true,"pushed_at":"2023-03-01T10:59:59.000Z","size":3737,"stargazers_count":12,"open_issues_count":21,"forks_count":1,"subscribers_count":1,"default_branch":"Main","last_synced_at":"2024-10-28T09:58:50.726Z","etag":null,"topics":["alexa","blazor","claims","efcore","grpc","grpc-web","identityserver4","kestrel","roles","sqllite","wasm","webassembly"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"JeepNL/Blazor-WASM-Identity-gRPC","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arjunkrishna.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2020-12-21T16:30:55.000Z","updated_at":"2023-11-01T21:57:10.000Z","dependencies_parsed_at":"2023-02-09T02:31:23.229Z","dependency_job_id":null,"html_url":"https://github.com/arjunkrishna/Blazor-WASM-Identity-gRPC-Alexa","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arjunkrishna","download_url":"https://codeload.github.com/arjunkrishna/Blazor-WASM-Identity-gRPC-Alexa/tar.gz/refs/heads/Main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243835952,"owners_count":20355611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alexa","blazor","claims","efcore","grpc","grpc-web","identityserver4","kestrel","roles","sqllite","wasm","webassembly"],"created_at":"2024-07-31T02:00:55.108Z","updated_at":"2025-03-16T06:30:22.533Z","avatar_url":"https://github.com/arjunkrishna.png","language":"C#","funding_links":[],"categories":["Sample Projects"],"sub_categories":["Authentication"],"readme":"\n\nThis repo has been forked from https://github.com/JeepNL/Blazor-WASM-Identity-gRPC and extended to accept alexa requests.\n\nIt uses Kestrel as the default webserver, a SQLite database and is \"*CTRL-F5'able*\" without any further configuration.\n\nYou can delete de SQLite database and migrations folder if you want and use the following commands in Visual Studio's Package Manager Console to re-create the db.\n\n1. Add-Migration InitialCreate\n2. Update-Database\n\nAt first run the app will create 2 users (_if they don't exist, see: Server/[SeedData.cs](BlazorTemplate/Server/Data/SeedData.cs)_)\n\n1. `admin@example.com` / `Qwerty1234#`\n2. `user@example.com` / `Qwerty1234#`\n\nand 2 roles: \n\n1. Users\n2. Administrators\n\nThe 'Administrators' \u0026amp; 'Users' roles will be assigned to: `admin@example.com`\n\nThe 'Users' role will be assigned to: `user@example.com`\n\n_Server/[ProfileService.cs](BlazorTemplate/Server/ProfileService.cs)_\n\n```csharp\n public ProfileService(UserManager\u003cApplicationUser\u003e userManager, IUserClaimsPrincipalFactory\u003cApplicationUser\u003e claimsFactory)\n        {\n            _userManager = userManager;\n            _claimsFactory = claimsFactory;\n        }\n\n        public async Task GetProfileDataAsync(ProfileDataRequestContext context)\n        {\n            var sub = context.Subject.GetSubjectId();\n            var user = await _userManager.FindByIdAsync(sub);\n            var principal = await _claimsFactory.CreateAsync(user);\n            var claims = principal.Claims.ToList();\n            \n            var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);\n            \n            var roles = await _userManager.GetRolesAsync(user);\n            var roleClaims = roles.Select(role =\u003e new Claim(JwtClaimTypes.Role, role));\n            \n            claims = claims.Where(claim =\u003e context.RequestedClaimTypes.Contains(claim.Type)).ToList();\n\n            // Add custom claims in token here based on user properties or any other source\n            claims.Add(new Claim(\"username\", user.UserName ?? string.Empty));\n            claims.AddRange(nameClaim);\n            claims.AddRange(roleClaims); \n            context.IssuedClaims = claims;\n        }\n```\n\n\n# Alexa Integration \n## Code Setup\n_Server/Controllers/[AlexaSkillController.cs](BlazorTemplate/Server/Controllers/AlexaSkillController.cs)_\n\n```csharp\n\t  [HttpPost(\"api/AlexaSkill/Request\")]\n        public IActionResult HandleResponse([FromBody] SkillRequest input)\n\n        {\n\n            var requestType = input.GetRequestType();\n            SkillResponse response = null;\n\n            var name = \"\";\n            var jwtEncodedString = input.Session.User.AccessToken;\n            if (jwtEncodedString is null)\n            {\n                response = ResponseBuilder.TellWithLinkAccountCard(\"You are not currently linked to this skill. Please go into your Alexa app and sign in.\");\n                response.Response.ShouldEndSession = true;\n\n                return new OkObjectResult(response);\n            }\n\n\n            var token = new JwtSecurityToken(jwtEncodedString: jwtEncodedString);\n            var claims = token.Claims;\n            name = claims.First(c =\u003e c.Type == \"name\").Value;\n\n\n            if (requestType == typeof(LaunchRequest))\n            {\n                response = ResponseBuilder.Tell($\"Welcome to Blazor News {name}!\");\n                response.Response.ShouldEndSession = false;\n            }\n\n            // return information from an intent\n            else if (requestType == typeof(IntentRequest))\n            {\n                // do some intent-based stuff\n                var intentRequest = input.Request as IntentRequest;\n                if (intentRequest.Intent.Name.Equals(\"news\"))\n                {\n                    // get the pull requests\n                    var news = GetNews();\n\n                    if (news == 0)\n                        response = ResponseBuilder.Tell(\"We have no blazor news at this time.\");\n                    else\n                        response = ResponseBuilder.Tell(\"There are \" + news.ToString() + \" blazor news articles.\");\n\n                    response.Response.ShouldEndSession = false;\n                }\n                else\n                {\n                    response = ResponseBuilder.Ask(\"I don't understand. Can you please try again?\", null);\n                    response.Response.ShouldEndSession = false;\n\n                }\n            }\n            else if (requestType == typeof(SessionEndedRequest))\n            {\n                response = ResponseBuilder.Tell(\"See you next time!\");\n                response.Response.ShouldEndSession = true;\n            }\n\n            return new OkObjectResult(response);\n        }\n\n        private static int GetNews()\n        {\n            return 3;\n        }\n\n```\n\n_Server/[Startup.cs](BlazorTemplate/Server/Startup.cs)_\n\nchanged made in StartUp.cs\n```csharp\n            var alexaVendor = Configuration[\"Alexa:BlazorNews:VendorId\"];\n            var alexaSecretText = \"AlexaBlazorNewsSecret\"; // I use this secret under the Alexa configuration.\n            var client = new IdentityServer4.Models.Client\n            {\n                ClientId = \"AlexaBlazorNews\",\n                ClientName = \"AlexaBlazorNews\",\n                Enabled = true,\n                AllowedGrantTypes = GrantTypes.Code,\n                AllowAccessTokensViaBrowser = true,\n                RequireConsent = false,\n                RequirePkce = false,\n                RequireClientSecret = true,\n                AllowRememberConsent = true,\n                ClientSecrets = {new Secret(alexaSecretText.Sha256()) },\n                RedirectUris =\n                {\n                    \"https://pitangui.amazon.com/api/skill/link/\" + alexaVendor,\n                    \"https://layla.amazon.com/api/skill/link/\" + alexaVendor,\n                    \"https://alexa.amazon.co.jp/api/skill/link/\"+alexaVendor\n                },\n                PostLogoutRedirectUris =\n                {\n                    \"https://pitangui.amazon.com/api/skill/link/\" + alexaVendor,\n                    \"https://layla.amazon.com/api/skill/link/\" + alexaVendor,\n                    \"https://alexa.amazon.co.jp/api/skill/link/\"+alexaVendor\n                },\n                AllowedScopes =\n                {\n                    IdentityServerConstants.StandardScopes.OpenId,\n                    IdentityServerConstants.StandardScopes.Profile,\n                    IdentityServerConstants.StandardScopes.Email,\n                    IdentityServerConstants.StandardScopes.Phone,\n                    \"alexa\"\n                },\n                AllowOfflineAccess = true,\n                AccessTokenType = AccessTokenType.Jwt,\n                \n            };\n\n            var clients = new List\u003cIdentityServer4.Models.Client\u003e();\n            var configClients = Configuration.GetSection(\"IdentityServer:Clients\")\n                .Get\u003cIdentityServer4.Models.Client[]\u003e();\n\n            clients.Add(client);\n            clients.AddRange(configClients);\n            \n            services.AddIdentityServer()\n                .AddApiAuthorization\u003cApplicationUser, ApplicationDbContext\u003e(options =\u003e\n                {\n                    options.IdentityResources[\"openid\"].UserClaims.Add(\"role\"); // Roles\n                    options.ApiResources.Single().UserClaims.Add(\"role\");\n                    options.IdentityResources[\"openid\"].UserClaims.Add(\"email\");\n                    options.ApiResources.Single().UserClaims.Add(\"email\");\n                    options.IdentityResources[\"openid\"].UserClaims.Add(\"name\");\n                    options.ApiResources.Single().UserClaims.Add(\"name\");\n                    options.Clients.AddRange(clients.ToArray()); \n                });\n            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove(\"role\");\n            services.AddTransient\u003cIProfileService, ProfileService\u003e();\n            services.AddControllersWithViews().AddNewtonsoftJson(); // newtonsoftjson is needed because alexa.net has not been migrated to Text.Json yet.\n```\n\n## NGrok Setup\n\n![Download Grok](img/2020-12-21_11-49-35.png)\n![NGrok Token Setup](img/2020-12-21_13-55-45.png)\n![NGROK command](img/2020-12-21_13-54-17.png)\n![NGrok url for forwarding](img/2020-12-21_13-56-31.png)\n\n## Alexa Console Setup\n\n![Alexa Create New Skill](img/2020-12-21_11-45-28.png)\n![Alexa Choose Template](img/2020-12-21_11-45-57.png)\n![Skill Builder Checklist](img/2020-12-21_11-46-32.png)\n![Skill Invocation](img/2020-12-21_11-47-33.png)\n![Create Intent](img/2020-12-21_11-48-11.png)\n![Utterances](img/2020-12-21_11-48-43.png)\n\n```\nDefault Region: https://a9afa2d4182f.ngrok.io/api/AlexaSkill/Request\n```\n\n![Endpoint](img/2020-12-21_11-54-46.png)\n\nNGrok url was changed as I had restarted the Ngrok. VendorId is picked from here.\n\n```\nWeb Authotization URI: https://a9afa2d4182f.ngrok.io/connect/authorize\nAccess Token URI: https://a9afa2d4182f.ngrok.io/connect/token\nClient ID: AlexaBlazorNews\nYour Secret: AlexaBlazorNewsSecret   (this will be used in the code)\nYour Authentication Scheme: I changed it to Credentials in request body. Still did not work. Need to read more.\n```\n![Account Linking](img/2020-12-21_12-03-15.png)\n\n\n\n## User Secret Setup\n\nVendorId setup\n![User Secret Setup](img/2020-12-21_12-50-42.png)\n\n## Blazor WASM\n\nRun Server Solution File from Visual Studio, NGrok will automatically pick it up.\n\n## Testing\n### Request\n![Test Development](img/2020-12-21_12-59-07.png)\n\n### Acount Linking via Alexa App\n\nLinking Screen in Alexa App on iOS\n\n![Alexa App](img/20201221_210439000_iOS.png)\n\nLogin Screen in the App\n\n![Alexa App Login](img/20201223_153742000_iOS.png)\n\nAlexa Account Linking Successful\n\n![Alexa Linking Successful](img/2020-12-21_16-03-10.png)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farjunkrishna%2FBlazor-WASM-Identity-gRPC-Alexa/lists"}