Open Closed

Modify tenant onboard flow by customizing tenant management module UI and API #5943


User avatar
0
ravick@cloudassert.com created
  • ABP Framework version: v7.3.2
  • UI Type: Angular
  • Database System: EF Core (SQL Server)
  • Tiered (for MVC) or Auth Server Separated (for Angular): yes
  • Exception message and full stack trace: -NA-
  • Steps to reproduce the issue: -NA-

We need to accomplish the following customization while onboarding a new tenant.

  • Display existing & extra properties of Identity module in create/edit tenant page. We are not sure how to display the existing or extra properties of the Identity module while creating the tenant. Along with Tenant admin's email address, Tenant admin's password, we need to get the Phone Number (available in Identity module) and Country (new extra property added in Identity Module). How to achieve this?

  • Customize the tenant onboarding flow As per the requirement, the end user will enter the Tenant and Tenant admin details and click on payment button. For payment, the user has to be redirected to stripe payment gateway. Upon successful payment, the tenant has to be created in database. Any idea on how to add this customization as part of onboarding flow?


26 Answer(s)
  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    is the tenant onboarding done without login to host user?

    to add extraproperties please follow below links

    https://docs.abp.io/en/abp/latest/Module-Entity-Extensions https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities https://docs.abp.io/en/abp/latest/Object-Extensions#addorupdateproperty

  • User Avatar
    0
    ravick@cloudassert.com created

    Hi

    is the tenant onboarding done without login to host user?

    Yes, it will be self-onboarding. How do I remove the Authentication for the APIs that are part of Tenant Management Module & Identity Module so that tenant can be onboarded without Host login.

    I referred the link https://docs.abp.io/en/abp/latest/Authorization#changing-permission-definitions-of-a-depended-module . But when the permission is disabled, it prohibits the access to the API.

    But in self-onboarding scenario, the API should be accessed anonymously.

  • User Avatar
    0
    jfistelmann created

    Are you really sure that you want to allow anonymous access to that?

    what is the exact flow?

    depending on your specific needs, you may want to create your own registration flow. Just a suggestion based on what you wrote:

    Upon registration, you may create a form where you ask for the information you need to build a tenant. Create your own appservice for that, where registration alone can be done without the requirement of being logged in. When the user registrates -> you create a tenant with the information you need, based on what the user specified. First create a tenant, then create a new user in that tenant.

    After that, you'll end up with a fresh tenant with a user inside. The rest can be done with an authorized user. You would not need to expose too many things.

    In addition, you may want to store information about the registration state to allow for clean up scenarios.


    In addition to the link you referred to: I think you can only change the permission needed, not disabling it entirely. You could try to add the [AllowAnonymous] attribute - but even if it works it would not be best practice to do that. Application Services handle specific needs. Your needs seem to be different from what the default provides. Therefore it's best to create your own implementation of Tenant/ User creation.

  • User Avatar
    0
    ravick@cloudassert.com created

    We are building a Saas product where an end user (as a tenant) can subscribe on their own. So, when they subscribe, we need to create a tenant and an admin user for them.

    If it is as per the default flow, then the host user has to get the details in offline from the end user, login to the portal to create respective tenant on behalf of end user.

    Any suggestions here?

    Even if we write own appservice, still it has to be anonymous, and the flow will remain the same.

    Also, we are not planning to expose all the Tenant APIs but only Create. Can you please share a code sample on how to override the existing permission of dependent module with [AllowAnonymous] attribute?

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    Yes instead of that you can create your own RegisterTenant Page in authserver and register a client with client_credentials flow

    Step 1 : In OpenIddictDataSeedContributor register a client wit client_credentials flow make sure you run migrator on a empty db or delete data from the openidApplication table

      // BookStore_Web_Account
      var bookStore_Web_AccountClientId = configurationSection["BookStore_Web_Account:ClientId"];
      if (!bookStore_Web_AccountClientId.IsNullOrWhiteSpace())
      {
          var bookStore_Web_AccountClientIdRootUrl = configurationSection["BookStore_Web_Account:RootUrl"]?.TrimEnd('/');
    
          await CreateApplicationAsync(
              name: bookStore_Web_AccountClientId!,
              type: OpenIddictConstants.ClientTypes.Confidential,
              consentType: OpenIddictConstants.ConsentTypes.Implicit,
              displayName: "Swagger Application",
              secret: configurationSection["BookStore_Web_Account:ClientSecret"] ?? "1q2w3e*",
              grantTypes: new List<string> {OpenIddictConstants.GrantTypes.ClientCredentials },
              scopes: new List<string> { "BookStore" },
              clientUri: bookStore_Web_AccountClientIdRootUrl,
              logoUri: "/images/clients/swagger.svg",
              permissions: new List<string>
              {
                  "Saas.Tenants",
                  "Saas.Tenants.Create",
                  "Saas.Tenants.Update",
                  "Saas.Tenants.ManageConnectionStrings",
                  "Saas.Tenants.SetPassword",
                  "Saas.Editions",
                  "Saas.Editions.Create",
                  "Saas.Editions.Update"
              }
          );
      }
    

    add this in dbmigrator appsetting.json

    Step 2 Create your own RegisterTenant.cshtml create UI as it is in Register.cshtml Page in the Volo.Account.Pro module you can download the source code from abp suite.

    in the RegisterTenant.cshtml.cs you can use this code make sure to create your own PostInput class just as it is in Register.cshtml.cs with the the extra fields that you need then you can use that to send it to tenant api..

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json.Linq;
    using System;
    using System.Collections.Generic;
    using System.Net.Http.Headers;
    using System.Net.Http;
    using System.Text;
    using System.Threading.Tasks;
    using Volo.Abp;
    using Volo.Abp.Account;
    using Volo.Abp.Account.Public.Web.Pages.Account;
    using Volo.Abp.Account.Public.Web.Security.Recaptcha;
    using Volo.Abp.Identity.Settings;
    using Volo.Abp.Settings;
    using Volo.Abp.Uow;
    using Volo.Saas.Host;
    using IdentityUser = Volo.Abp.Identity.IdentityUser;
    using Microsoft.Extensions.Configuration;
    using System.Text.Json;
    
    namespace Acme.BookStore.Pages.Account
    {
        public class RegisterTenantModel : RegisterModel
        {
            private readonly IConfiguration _configuration;
    
            public RegisterTenantModel(IConfiguration configuration)
            {
                _configuration = configuration;
            }
    
            protected override async Task< IdentityUser > RegisterLocalUserAsync()
            {
                ValidateModel();
    
                var captchaResponse = string.Empty;
                if (UseCaptcha)
                {
                    captchaResponse = HttpContext.Request.Form[RecaptchaValidatorBase.RecaptchaResponseKey];
                }
    
                string token = await GetClientCredentialsTokenAsync($"{_configuration["AuthServer:Authority"]?.EnsureEndsWith('/')}connect/token" ?? string.Empty, "BookStore_Web_Account", "1q2w3e*", new string[] { "BookStore" });
    
    
    
    
                var tenant = new
                {
                    name = "demo",
                    adminEmailAddress = "demo@demo.com",
                    adminPassword = "Welcome1$"
                };
    
                var tenantId = await CreateTenant(token, JsonSerializer.Serialize(tenant));
    
                using (CurrentTenant.Change(tenantId))
                {
                    var user = await UserManager.FindByEmailAsync("example@example.com");
    
                    if (user == null)
                    {
                        throw new UserFriendlyException("UserNotFound");
                    }
                    user.SetPhoneNumber("9999999999", false);
                    user.Name = "Demo";
                    user.Surname = "Demo";
                    await UserManager.UpdateAsync(user);
    
                    return user;
                }
            }
    
            private async Task< Guid? > CreateTenant(string token,string body)
            {
                using (HttpClient client = new HttpClient())
                {
                    var request = new HttpRequestMessage(HttpMethod.Post, $"{_configuration["RemoteServices:BaseUrl"]?.EnsureEndsWith('/')}api/saas/tenants");
                    request.Headers.Add("Authorization", $"Bearer {token}");
    
                    request.Content = new StringContent(body, null, "application/json");
                    var response = await client.SendAsync(request);
                    response.EnsureSuccessStatusCode();
                    var responseBody = await response.Content.ReadAsStringAsync();
                    var responseDeserilized = JsonSerializer.Deserialize<TenantResponse>(responseBody);
    
                    return responseDeserilized?.id;
                }
            }
            private async Task< string > GetClientCredentialsTokenAsync(string tokenUrl, string clientId, string clientSecret, IEnumerable<string> scopes = null)
            {
                // Create a HttpClient to send the request to the token endpoint
                using (HttpClient client = new HttpClient())
                {
                    // Set the client credentials (client_id and client_secret) in the request headers
                    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
    
                    // Prepare the request content for the token endpoint
                    List<KeyValuePair< string, string > > requestContent = new List<KeyValuePair<string, string>>
            {
                new KeyValuePair<string, string>("grant_type", "client_credentials"),
            };
    
                    // Add scopes, if provided
                    if (scopes != null)
                    {
                        requestContent.Add(new KeyValuePair<string, string>("scope", string.Join(" ", scopes)));
                    }
    
                    // Send the request to the token endpoint
                    HttpResponseMessage response = await client.PostAsync(tokenUrl, new FormUrlEncodedContent(requestContent));
    
                    // Check if the request was successful (status code 200)
                    if (response.IsSuccessStatusCode)
                    {
                        // Read the response content (which should contain the access token)
                        string responseContent = await response.Content.ReadAsStringAsync();
    
                        // Deserialize the response to extract the access token
                        dynamic responseData = JObject.Parse(responseContent);
                        string accessToken = responseData.access_token;
    
                        return accessToken;
                    }
                    else
                    {
                        // If the request was not successful, handle the error here
                        // You might want to log or throw an exception
                        throw new HttpRequestException($"Error getting access token. Status code: {response.StatusCode}");
                    }
                }
            }
    
        }
        public class TenantResponse
        {
            public Guid id { get; set; }
        }
    }
    
    

    in this way you avoid making anything public or anonymous and only will be doable on the registeration page

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Are you really sure that you want to allow anonymous access to that?

    what is the exact flow?

    depending on your specific needs, you may want to create your own registration flow. Just a suggestion based on what you wrote:

    Upon registration, you may create a form where you ask for the information you need to build a tenant. Create your own appservice for that, where registration alone can be done without the requirement of being logged in. When the user registrates -> you create a tenant with the information you need, based on what the user specified. First create a tenant, then create a new user in that tenant.

    After that, you'll end up with a fresh tenant with a user inside. The rest can be done with an authorized user. You would not need to expose too many things.

    In addition, you may want to store information about the registration state to allow for clean up scenarios.


    In addition to the link you referred to: I think you can only change the permission needed, not disabling it entirely. You could try to add the [AllowAnonymous] attribute - but even if it works it would not be best practice to do that. Application Services handle specific needs. Your needs seem to be different from what the default provides. Therefore it's best to create your own implementation of Tenant/ User creation.

    jfistelmann i will look into this. thanks

  • User Avatar
    0
    ravick@cloudassert.com created

    Yes, we will have Captcha as well.

    We are using Angular as front end. Do you have any code samples in Angular?

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    to enable captcha

    you follow this

    https://support.abp.io/QA/Questions/489/How-to-enable-reCaptcha-in-ABP?_ga=2.177433699.75017784.1696827709-327044615.1686624402

    i am confused why are you asking for angular code snippets, can you please explain?

    read more about account module

    https://docs.abp.io/en/commercial/latest/modules/account

    All these you can override in AuthServer Project which is in mvc the previous registeration code that i share you have to put that in MVC authserver project.

    read more about https://docs.abp.io/en/commercial/latest/modules/payment

    if you want to do the registration in angular. you just have to do the convert the code inside RegisterLocalUserAsync() to angular which are basically httpcalls, please refer to angular documentation about how to make http requests https://angular.io/api/common/http/HttpClient

  • User Avatar
    0
    ravick@cloudassert.com created

    Hi Anjali,

    Getting bad request when trying to get the token in GetClientCredentialsTokenAsync with scope. Here my request content

    Without scope, token created successfully but since it doesn't have necessary scope, it fails with 401 when trying to create tenant. But we need scope and also CreateApplicationAsync checks for scope and throws error if not found.

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    have you assigned scopes to the client created in the OpenIddictDataSeedContributor class?

    i have have update the previous answer https://support.abp.io/QA/Questions/5943#answer-3a0e2ff0-2171-cf2a-60f2-c4164551b1cf please go through it again

  • User Avatar
    0
    ravick@cloudassert.com created

    Yes, it got seeded successfully in database as well.

    Here is the code

    Seed Code

     //Auth Server Client
     var authClientId = configurationSection["Application_Auth:ClientId"];
     if (!authClientId.IsNullOrWhiteSpace())
     {
         var authClientIdRootUrl = configurationSection["Application_Auth:RootUrl"]?.TrimEnd('/');
    
         await CreateApplicationAsync(
             name: authClientId!,
             type: OpenIddictConstants.ClientTypes.Confidential,
             consentType: OpenIddictConstants.ConsentTypes.Implicit,
             displayName: "Auth Application",
             secret: configurationSection["Application_Auth:ClientSecret"] ?? "1q2w3e*",
             grantTypes: new List&lt;string&gt; { OpenIddictConstants.GrantTypes.ClientCredentials },
             scopes: new List&lt;string&gt; { "BookStore" },
             clientUri: authClientIdRootUrl,
             logoUri: "/images/clients/swagger.svg",
             permissions: new List&lt;string&gt;
             {
           "Saas.Tenants",
           "Saas.Tenants.Create",
           "Saas.Tenants.Update",
           "Saas.Tenants.ManageConnectionStrings",
           "Saas.Tenants.SetPassword",
           "Saas.Editions",
           "Saas.Editions.Create",
           "Saas.Editions.Update"
             }
         );
     }
     
    

    appsettings.json

      "Application_Auth": {
        "ClientId": "Application_Auth",
        "ClientSecret": "1q2w3e*",
        "RootUrl": "https://localhost:44370"
      },
      
    

    RegisterTeanantModel.cs

    public class TempTenant
    {
        public Guid id { get; set; }
    }
    
    public class RegisterTenantModel : RegisterModel
    {
        private readonly IConfiguration _configuration;
    
        public RegisterTenantModel(IConfiguration configuration)
        {
            _configuration = configuration;
        }
    
        protected override async Task&lt;IdentityUser&gt; RegisterLocalUserAsync()
        {
            //ValidateModel();
    
            //var captchaResponse = string.Empty;
            //if (UseCaptcha)
            //{
            //    captchaResponse = HttpContext.Request.Form[RecaptchaValidatorBase.RecaptchaResponseKey];
            //}
    
            string token = await GetClientCredentialsTokenAsync($"{_configuration["AuthServer:Authority"]?.EnsureEndsWith('/')}connect/token" ?? string.Empty, "HealthySmiles_Auth", "1q2w3e*", new string[] { "BookStore" });
    
    
            var tenant = new
            {
                name = "demo",
                adminEmailAddress = "demo@demo.com",
                adminPassword = "Welcome1$"
            };
    
            var tenantId = await CreateTenant(token, JsonSerializer.Serialize(tenant));
    
            using (CurrentTenant.Change(tenantId))
            {
                var user = await UserManager.FindByEmailAsync("example@example.com");
    
                if (user == null)
                {
                    throw new UserFriendlyException("UserNotFound");
                }
                user.SetPhoneNumber("9999999999", false);
                user.Name = "Demo";
                user.Surname = "Demo";
                await UserManager.UpdateAsync(user);
    
                return await UserManager.GetByIdAsync(user.Id);
            }
        }
    
        private async Task&lt;Guid?&gt; CreateTenant(string token, string body)
        {
            using (HttpClient client = new HttpClient())
            {
                var apiUrl = ""+"/api/saas/tenants";
                //var request = new HttpRequestMessage(HttpMethod.Post, $"{_configuration["RemoteServices:BaseUrl"]?.EnsureEndsWith('/')}api/saas/tenants");
                var request = new HttpRequestMessage(HttpMethod.Post, "https://localhost:44396/api/saas/tenants");
                request.Headers.Add("Authorization", $"Bearer {token}");
    
                request.Content = new StringContent(body, null, "application/json");
                var response = await client.SendAsync(request);
                response.EnsureSuccessStatusCode();
                var responseBody = await response.Content.ReadAsStringAsync();
                var responseDeserilized = JsonSerializer.Deserialize&lt;TempTenant&gt;(responseBody);
    
                return responseDeserilized?.id;
            }
        }
        private async Task&lt;string&gt; GetClientCredentialsTokenAsync(string tokenUrl, string clientId, string clientSecret, IEnumerable scopes = null)
        {
            // Create a HttpClient to send the request to the token endpoint
            using (HttpClient client = new HttpClient())
            {
                // Set the client credentials (client_id and client_secret) in the request headers
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")));
    
                // Prepare the request content for the token endpoint
    
    
    
    
                var requestContent = new List&lt;KeyValuePair&lt;string, string&gt;>() {
    
                    new KeyValuePair&lt;string,string&gt;("grant_type", "client_credentials") };
    
                // Add scopes, if provided
                if (scopes != null)
                {
                    requestContent.Add(new KeyValuePair&lt;string, string&gt;("scope", string.Join(" ", scopes)));
                }
    
                // Send the request to the token endpoint
                HttpResponseMessage response = await client.PostAsync(tokenUrl, new FormUrlEncodedContent(requestContent));
    
                // Check if the request was successful (status code 200)
                if (response.IsSuccessStatusCode)
                {
                    // Read the response content (which should contain the access token)
                    string responseContent = await response.Content.ReadAsStringAsync();
    
                    // Deserialize the response to extract the access token
                    dynamic responseData = JObject.Parse(responseContent);
                    string accessToken = responseData.access_token;
    
                    return accessToken;
                }
                else
                {
                    // If the request was not successful, handle the error here
                    // You might want to log or throw an exception
                    throw new HttpRequestException($"Error getting access token. Status code: {response.StatusCode}");
                }
            }
        }
    
    }
    public class TenantResponse
    {
        public Guid id { get; set; }
    }
    
  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    you have to put your scopes not the Bookstore.

  • User Avatar
    0
    ravick@cloudassert.com created

    This is the scope I see in my table, and I have provided the same in the code as well.

    Though I am able to create token, it fails with 401 error code when trying to create tenant using the API api/saas/tenants

    I tried both "HealthySmiles" as well as commonScopes yet 401 unauthorized error.

    Is there anything to add or remove in the permission list?

    This is turning out to be a showstopper for the release :(

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    i see that you added bookstore as a scope it should be HealthySmiles

    also can you run this query and see if you have assigned permission to the client

    select * from [dbo].[AbpPermissionGrants] where ProviderKey = 'HealthySmiles_Auth'

    for me i get

  • User Avatar
    0
    ravick@cloudassert.com created

    As mentioned in the earlier post, I mentioned the scope as HealthySmiles and passing the scope as HealthySmiles too string token = await GetClientCredentialsTokenAsync($"{_configuration["AuthServer:Authority"]?.EnsureEndsWith('/')}connect/token" ?? string.Empty, "HealthySmiles_Auth", "1q2w3e*", new List<string> { "HealthySmiles" });

    DB records

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    can you decode your access_token on https://jwt.io/ ? or please share the acess_token

  • User Avatar
    0
    ravick@cloudassert.com created

    Here is the token

    eyJhbGciOiJSUzI1NiIsImtpZCI6IjkwM0E5OUVFRjI2NTJERDMyM0EwOUM2Q0NEMjM1ODU2MzVGOEUzQzAiLCJ4NXQiOiJrRHFaN3ZKbExkTWpvSnhzelNOWVZqWDQ0OEEiLCJ0eXAiOiJhdCtqd3QifQ.eyJvaV9wcnN0IjoiSGVhbHRoeVNtaWxlc19BdXRoIiwiY2xpZW50X2lkIjoiSGVhbHRoeVNtaWxlc19BdXRoIiwib2lfdGtuX2lkIjoiODc2MzlkYWEtYWQ1Zi0yNGUxLTliYTItM2EwZTM1OWYyNWJlIiwianRpIjoiYTkwNThkMDctMzFmMS00NzIwLWI1M2ItODcyNDEyODE0NzJmIiwiZXhwIjoxNjk3MTEwMzc1LCJpc3MiOiJodHRwczovL2xvY2FsaG9zdDo0NDM3MC8iLCJpYXQiOjE2OTcxMDY3NzV9.R6Fb44yErOlKj-FE_7CX-GrsmmDtv3BZgXCkWjeq02Mkyr3MEkB33eHydA6iEYblDPeqvHxmjQD3dgzJisTS9YTUe52qF8GwzE-PYIeUas37ejEdvA8JIs5VwMtxe4q_FOa2X9gffQihYtWdXd8I2doZVO-iYhp3l7VUHcTpQh_zcqP1bNonqv4ES5noHEizHb3ZPPTIFByAOoq-Eiu0fvXaZ_lOwNbiqHQNlShsPLk5ViEDVmkKGOrdbtwaLlyHh4H5LSKvUfq2oCfQilMoKeg0xg-_Ar-wDF_rH4topL-wgrJPGN2bVVXOffO66OM6lWT4vERHqgUEXBILUTaIpg

    Upon further analysing the 401 issue I found inner exception as

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    i see difference between my token and your toke there is no scope in your token

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    Can you share your In BookStoreAuthServerModule

    in BookStoreHttpApiHostModule

  • User Avatar
    0
    ravick@cloudassert.com created

    Hello Anjali

    I am getting bad request if I add scope Please see the code and the values passed in watch

    Bad Request as response

    <br>

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    Can you share your
    In BookStoreAuthServerModule

    in BookStoreHttpApiHostModule

    Also please share these

    on your browser can you visit

    https://localhost:44370/.well-known/openid-configuration

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    Is it possible to share your solution on support@abp.io with the ticket id or share a google drive link? I'll have a look what is it missing

  • User Avatar
    0
    ravick@cloudassert.com created

    HealthySmilesAuthServerModule

    https://localhost:44370/.well-known/openid-configuration { "issuer": "https://localhost:44370/", "authorization_endpoint": "https://localhost:44370/connect/authorize", "token_endpoint": "https://localhost:44370/connect/token", "introspection_endpoint": "https://localhost:44370/connect/introspect", "end_session_endpoint": "https://localhost:44370/connect/logout", "revocation_endpoint": "https://localhost:44370/connect/revocat", "userinfo_endpoint": "https://localhost:44370/connect/userinfo", "device_authorization_endpoint": "https://localhost:44370/device", "jwks_uri": "https://localhost:44370/.well-known/jwks", "grant_types_supported": [ "authorization_code", "implicit", "password", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "LinkLogin", "Impersonation" ], "response_types_supported": [ "code", "code id_token", "code id_token token", "code token", "id_token", "id_token token", "token", "none" ], "response_modes_supported": [ "form_post", "fragment", "query" ], "scopes_supported": [ "openid", "offline_access", "email", "profile", "phone", "roles", "address", "HealthySmiles" ], "claims_supported": [ "aud", "exp", "iat", "iss", "sub" ], "id_token_signing_alg_values_supported": [ "RS256" ], "code_challenge_methods_supported": [ "S256" ], "subject_types_supported": [ "public" ], "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ], "introspection_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ], "revocation_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ], "device_authorization_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post" ], "claims_parameter_supported": false, "request_parameter_supported": false, "request_uri_parameter_supported": false, "authorization_response_iss_parameter_supported": true }

  • User Avatar
    0
    Anjali_Musmade created
    Support Team Support Team Member

    Hi

    Is it possible to share your solution on support@abp.io with the ticket id or share a google drive link? I'll have a look what is it missing

    Hi can share your project I'll have a look.

  • User Avatar
    0
    ravick@cloudassert.com created

    Upon analysing further, by changing the below line in the method GetClientCredentialsTokenAsync

    HttpResponseMessage response = await client.PostAsync(tokenUrl, new FormUrlEncodedContent(requestContent));

    to

    var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { Address = tokenUrl, ClientId = clientId, ClientSecret = clientSecret, Scope = "HealthySmiles", }

    has helped to resolve the Bad Request issue and was able to create the tenant successfully.

    Will let you know if there are any issues during actual implementation

Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11