Blazor WASM Security Odata get token

Hi,

I would like to query some data from a winforms application.

I secured the odata described in the documentation:

With a valid token I am able to query data from the winforms application.
But I am not able to get the token with a web request or something else?
Maybe you have an url and how to post data to get the token? Thanks.

Radzen Blazor Wasm applications use Identity Server (as the dotnet new template). You can check Identity Server's documentation (or search online) how to get a token from it. The Radzen application itself does not have a dedicated API for this.

Hi,

the last days I read the documentation but it does not work for me.

Is there another way to consume the radzen api with the users?
Now I am using the lightswitch odata, this is very easy to authenticate.
I use the winforms app and the server app local, so security is not very imported.
I have to login and to track changes from users.
Thanks.

There isn't another way.

What prevents you from using the same approach with the OData service generated by Radzen?

Hi,

I need a token to query the odata if it is secured.
With Lightswitch Odata it is very easy to log in a user.

With radzen and identity server it is more complex. The navigationmanager handles the login and the redirect.
I tried really many different requests with the httpclient from winforms to get the token, but it does not work. I cannot find the method on the server which creates the token for the user. Otherwise it would be possible to make a request with username and password and send the token back.
I think the identity server uses for this the authentication.js file?
Thanks.

We don't know how this works in Lightswitch. How do you login from Winforms?

What did you try? Here is an example from the Identity Server documentation: https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html Also this question contains some useful code: IdentityServer4 with Hybrid WinForms OidcClient2 - auth flow opens browser after login - Stack Overflow

Hi,

thanks for the links.

I read the article: Protecting an API using Client Credentials — IdentityServer4 1.0.0 documentation

But in the radzen project is no config.cs for defining the clients?
I found the clientid in the mssql table PersistedGrants and a key.
Is this key the client secret?

In the discovery document the grant_type pasword is enabled.
So it should be possible to log in with user and password:
https://docs.identityserver.io/en/release/quickstarts/2_resource_owner_passwords.html

With this code it should be possible to get the token:

// request token
var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password", "api1");

if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}

Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");

But I do not know the client secret? Thanks.

The client is defined as per the default Microsoft template in the appsettings.json:

  "IdentityServer": {
    "Clients": {
      "MultiTenantWebAssembly.Client": {
        "Profile": "IdentityServerSPA"
      }
    }
  }

Thanks.

Now I added a config.cs File and a scope and a client.

I added AddinMemoryClients and AddInMemoryApiScopes to the IdentityServer in the Startup.cs

services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationIdentityDbContext>().AddInMemoryClients(Config.Clients).AddInMemoryApiScopes(Config.ApiScopes);
            services.AddTransient<IdentityServer4.Services.IProfileService, ProfileService>();
            services.AddAuthentication()
                .AddIdentityServerJwt();

I tried to get a token from my winforms app with the client id and the secret and it works.

But I would to have a token for my radzen users.
For example I have the user markus@gmail.com with my password.

I tried to get a token with the user and password, but there is an error that the client is unauthorized.
Maybe I have to add the users also to the identity server, but how?
Thanks.

The users are already added to identity server otherwise you wouldn't be able to login with them in the Radzen application.

I found another blog post which might help: IdentityServer4, ASP.NET Core API and a client with username/password - it should log a winforms app with a username and password:

var identityServerResponse = await httpClient.RequestPasswordTokenAsync(new PasswordTokenRequest
{
    Address = "http://localhost:5000/connect/token",
    GrantType = "password",

    ClientId = "ConsoleApp_ClientId",
    ClientSecret = "secret_for_the_consoleapp",
    Scope = "ApiName",

    UserName = username,
    Password = password.ToSha256()
});

Now I have got the solution :slight_smile:

In the client in the config File I had to change the AllowedGrantTypes to GrantTypes.ResourceOwnerPassword

public static IEnumerable<IdentityServer4.Models.Client> Clients =>
            new List<IdentityServer4.Models.Client>
            {

                new IdentityServer4.Models.Client
                {
                    ClientId = "client",

                    // no interactive user, use the clientid/secret for authentication
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                    // secret for authentication
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    

                    // scopes that client has access to
                    AllowedScopes = { "api1" }

                }
            };

Following Code I am using in my Winforms Application:

string password = "Password123";
            var client = new HttpClient();

            var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
            {
                Address = "https://localhost:5001/connect/token",

                ClientId = "client",
                ClientSecret = "secret",
                Scope = "api1",
                GrantType = "password",
                UserName = "markus@gmail.com",
                Password = password
            });

There is a chance this can be done in appsettings.json without a separate config file.

I tried to do this. It is described here:
https://docs.identityserver.io/en/latest/topics/clients.html

But I get an error when start debugging.

Now I managed to define the client in the appsettings.json:

"IdentityServer": {
    "Clients": {
      "TestwbaSecurity.Client": {
        "Enabled": true,
        "Profile": "IdentityServerSPA",
        "ClientId": "ro.client",
        "ClientSecrets": [ { "Value": "<Insert Sha256 hash of the secret encoded as Base64 string>" } ],
        "AllowedGrantTypes": [ "password" ],
        "AllowedScopes": [ "openid", "profile", "resource1.scope1", "resource1.scope2" ]
      }
    }
  }

But there is a problem with the user login in the radzen application if I add the inmemoryusers to the identityserver.

services.AddIdentityServer()                
                .AddApiAuthorization<ApplicationUser, ApplicationIdentityDbContext>().AddInMemoryClients(Configuration.GetSection("IdentityServer:Clients"));

I have the same problem adding the users via the config.cs file in the startup.cs code.

There is no default client with a default password? Thanks.

This probably overrides the database users. Unfortunately this is way beyond our experience with Identity Server and we can't comment further.

Finaly I have got the solution. It is working :slight_smile:

First change the startup and add the client in the startup:

services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationIdentityDbContext>(options =>
                options.Clients.Add(new IdentityServer4.Models.Client()
                {
                    ClientId = "client",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    AccessTokenType = AccessTokenType.Jwt,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    // scopes that client has access to
                    AllowedScopes = { "TestwbaSecurity.ServerAPI openid profile" }
                }));

Next Step is to request the token in the winforms app:

           string password = "password123";
            var client = new HttpClient();
            string secret = "secret".ToSha256();

            var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
            {
                Address = "https://localhost:5001/connect/token",

                ClientId = "client",
                ClientSecret = "secret",
                Scope = "TestwbaSecurity.ServerAPI openid profile",
                GrantType = "password",
                UserName = "markus@gmail.com",
                Password = password
            });

            token = JsonConvert.DeserializeObject<BearerToken>(response.Json.ToString());

And adding the token to the odata request

dataContext.SendingRequest2 += async (s, f) =>
            {
                string authHeaderValue = "Bearer " + token.AccessToken;
                f.RequestMessage.SetHeader("Authorization", authHeaderValue);
            };

Thanks for sharing a working solution! Did you try adding the client in the appsettings.json file?

"IdentityServer": {
    "Clients": {
      "TestwbaSecurity.Client": {
        "Enabled": true,
        "Profile": "IdentityServerSPA",
        "ClientId": "ro.client",
        "ClientSecrets": [ { "Value": "<Insert Sha256 hash of the secret encoded as Base64 string>" } ],
        "AllowedGrantTypes": [ "password" ],
        "AllowedScopes": [ "openid", "profile", "resource1.scope1", "resource1.scope2" ]
      },
      "client": {
        "AllowedScopes": [ "openid", "profile", "TestwbaSecurity.ServerAPI" ]
        "ClientSecrets": [ { "Value": "<Insert Sha256 hash of the secret encoded as Base64 string>" } ],
         "AllowedGrantTypes": [ "password" ]
      }
    }
  }

If you tried and couldn't get it to work try using a custom Startup.cs (with a partial class) in order to avoid adding Startup.cs to ignore list.

When I add the client in the appsettings.json I have to add the inmemoryclients in the startup.cs. This will override the standard users. So it is not possible.

I tried to add the client in te costum startup but it is not possible with my code, because it is adding another IdentityServer and this is not possible.

services.AddIdentityServer()
                .AddApiAuthorization<ApplicationUser, ApplicationIdentityDbContext>(options =>
                options.Clients.Add(new IdentityServer4.Models.Client()
                {
                    ClientId = "client",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
                    AccessTokenType = AccessTokenType.Jwt,
                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },
                    // scopes that client has access to
                    AllowedScopes = { "TestwbaSecurity.ServerAPI openid profile" }
                }));

But I do not know how to access the options of the ApiAuthorization in the Custom startup? Because in the options of the Apiauthorization I have to add the client.
Maybe you can help me with this? Thanks.

As far as I understand IdentityServer's configuration you should be able to add multiple clients from the appsettings.json file without the need for inmemoryclients. I am not sure why it isn't working for you as I lack the required IdentityServer experience.

It is described here that I have to Add inmemoryclients.

https://docs.identityserver.io/en/latest/topics/clients.html