Click here to Skip to main content
15,887,175 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I am encountering difficulties with my Duende Identity Server implementation. The server successfully provides tokens to a WPF Desktop client, but I'm facing issues when trying to obtain an access token from the server using a mobile app.

I decided to use Duende Identity Server because the old IdentityServer4 is no longer supported. However, Duende Identity is still new and I rarely find any support on the Internet.

My project consists of:

A Duende Identity server that is responsible for giving tokens to native apps like: Desktop and Mobile.
A Protected API
A WPF Desktop client
A Mobile App client
This is my code:
Identity Server

Program.cs:
C#
using AuthenticationServer;
using AuthenticationServer.Domain.Models;
using AuthenticationServer.Mapping;
using AuthenticationServer.Persistence;
using AuthenticationServer.Persistence.Seeders;
using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

#region Configure services
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll",
builder =>
{
builder
.WithOrigins("http://localhost:3000", "https://authenticationserver2023.azurewebsites.net")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});

builder.Services.AddControllers();
builder.Services.AddControllersWithViews();

builder.Services.AddAutoMapper(typeof(ModelToViewModelProfile));

builder.Services.AddDbContext(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("CloudConnection"));
});

builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireDigit = false;
})
.AddEntityFrameworkStores()
.AddDefaultTokenProviders();

builder.Services
.AddIdentityServer(options =>
{
options.EmitStaticAudienceClaim = true;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b =>
b.UseSqlServer(builder.Configuration.GetConnectionString("CloudConnection"),
b => b.MigrationsAssembly("AuthenticationServer"));
})
.AddConfigurationStoreCache()
.AddProfileService()
.AddAspNetIdentity();

builder.Services.AddLocalApiAuthentication();

builder.Services.AddSingleton((container) =>
{
var logger = container.GetRequiredService<ILogger>();
return new DefaultCorsPolicyService(logger)
{
AllowAll = true
};
});
builder.Services.AddScoped<IProfileService, ProfileService>();
builder.Services.AddScoped();
#endregion

var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
using var identityServerSeeder = scope.ServiceProvider.GetService();
identityServerSeeder?.SeedData();
}

if (args.Length == 1 && args[0].ToLower() == "seeddata")
{
await SeedUsersRoles.SeedUsersAndRolesAsync(app);
}

app.UseCors("AllowAll");
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseIdentityServer();

app.UseStaticFiles();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
});

app.Run();

Config.cs
C#
<pre>using Duende.IdentityServer;
using Duende.IdentityServer.Models;

namespace AuthenticationServer;

public static class Config
{
public static IEnumerable IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
};

public static IEnumerable<ApiScope> ApiScopes =>
    new List<ApiScope>
    {
        new ApiScope("native-client-scope"),
        new ApiScope(IdentityServerConstants.LocalApi.ScopeName),
    };

public static IEnumerable<Client> Clients =>
    new List<Client>
    {
        new Client {
                ClientId = "native-client",

                AllowedGrantTypes = GrantTypes.Code,
                RequirePkce = true,
                RequireClientSecret = false,

                //RedirectUris = { "https://localhost:7124/account/login" },
                RedirectUris = { "https://authenticationserver2023.azurewebsites.net/account/login" },
                PostLogoutRedirectUris = { },
                AllowedCorsOrigins = { "http://localhost", "https://authenticationserver2023.azurewebsites.net" },

                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId,
                    "native-client-scope",
                    IdentityServerConstants.LocalApi.ScopeName,
                    IdentityServerConstants.StandardScopes.Profile
                },

                AllowAccessTokensViaBrowser = true,
                RequireConsent = false,
                AccessTokenLifetime = 8*3600
            },
        new Client {
                ClientId = "user-management-app",

                AllowedGrantTypes = GrantTypes.Code,
                RequirePkce = true,
                RequireClientSecret = false,

                RedirectUris = { "https://authenticationserver2023.azurewebsites.net/account/login" },
                PostLogoutRedirectUris = { },
                AllowedCorsOrigins = { "https://authenticationserver2023.azurewebsites.net" },

                AllowedScopes = {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.LocalApi.ScopeName,
                    IdentityServerConstants.StandardScopes.Profile
                },

                AllowAccessTokensViaBrowser = true,
                RequireConsent = false,
            },
    };
}


AccountController.cs
C#
<pre>using AuthenticationServer.Domain.Models;
using AuthenticationServer.ViewModels;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;

namespace AuthenticationServer.Controllers;
public class AccountController : Controller
{
private readonly SignInManager _signInManager;

public AccountController(SignInManager<ApplicationUser> signInManager)
{
    _signInManager = signInManager;
}

[HttpGet]
public IActionResult Login()
{
    return View();
}

[HttpPost]
public async Task<IActionResult> Login(LoginInputModel model, [FromQuery] string returnUrl)
{
    var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, false, false);
    if (result.Succeeded)
    {
        return Redirect(returnUrl);
    }

    return View(true);
}



Mobile:
Dart
<pre>import 'package:flutter/material.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:http/http.dart' as http;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@OverRide
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter AppAuth Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
@OverRide
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
final FlutterAppAuth _appAuth = FlutterAppAuth();
String? _accessToken;
String? _idToken;
String? _userInfo;
bool _isBusy = false;

@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter AppAuth Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _isBusy ? null : _signIn,
child: const Text('Sign In'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _accessToken != null ? _callApi : null,
child: const Text('Call API'),
),
const SizedBox(height: 16),
if (_accessToken != null) Text('Access Token: $_accessToken'),
const SizedBox(height: 8),
if (_idToken != null) Text('ID Token: $_idToken'),
const SizedBox(height: 8),
if (_userInfo != null) Text('User Info: $_userInfo'),
const SizedBox(height: 8),
if (_isBusy) CircularProgressIndicator(),
],
),
),
);
}

Future _signIn() async {
try {
setState(() {
_isBusy = true;
});

  final AuthorizationTokenResponse? result =
      await _appAuth.authorizeAndExchangeCode(
    AuthorizationTokenRequest(
      'native-client',
      'https://authenticationserver2023.azurewebsites.net/account/login',
      serviceConfiguration: const AuthorizationServiceConfiguration(
        tokenEndpoint:
            'https://authenticationserver2023.azurewebsites.net/connect/token',
        authorizationEndpoint:
            'https://authenticationserver2023.azurewebsites.net/connect/authorize',
      ),
      scopes: ['openid', 'profile', 'native-client-scope'],
      promptValues: ['login'],
      issuer: 'https://authenticationserver2023.azurewebsites.net',
    ),
  );
  if (result != null) {
    _processAuthTokenResponse(result);
    await _callApi();
  }
} catch (e) {
  print('Error during sign in: $e');
} finally {
  setState(() {
    _isBusy = false;
  });
}
}

Future _callApi() async {
try {
final http.Response httpResponse = await http.get(
Uri.parse('https://protectedapi2023.azurewebsites.net/WeatherForecast'),
headers: <String, String>{'Authorization': 'Bearer $_accessToken'},
);

  setState(() {
    _userInfo =
        httpResponse.statusCode == 200 ? httpResponse.body : 'API Error';
  });
} catch (e) {
  print('Error calling API: $e');
}
}

void _processAuthTokenResponse(AuthorizationTokenResponse response) {
setState(() {
_accessToken = response.accessToken;
_idToken = response.idToken;
});
}
}


What I have tried:

When using Mobile app to access the account/login endpoint and type in the correct username and password, the returnUrl parameter is happened to be null and then I got error 500
Posted
Updated 24-Nov-23 7:05am
v4
Comments
[no name] 21-Nov-23 12:21pm    
Your work in WPF has nothing to do with what's happening in your mobile app from what I can see. Other than the "end point", they have nothing in common. So, no point tagging your question as "C#, WPF".
anonymous from Hanoi 22-Nov-23 9:09am    
thank you for your feedback. I have modified my question.
[no name] 22-Nov-23 17:24pm    
I don't know what devices "Dart and Flutter" run on, but if you can get it to work in C#, then create a C# (proxy) server (on the device or wherever).
anonymous from Hanoi 23-Nov-23 9:26am    
My "Dart and Flutter" code is running on Mobile device (OS: Androids).
[no name] 24-Nov-23 14:21pm    
That's what I said: create a C# proxy server on your device: https://www.techworm.net/2022/04/windows-11-android-smartphone.html

1 solution

For Identity, cookies are okay for non-mobile applications. For mobile, you will need to use Bearer tokens.
 
Share this answer
 
Comments
anonymous from Hanoi 25-Nov-23 1:27am    
Thank you for your solution, sir. Could you make it clearer? What should I modify to make it work?
Graeme_Grant 25-Nov-23 2:07am    
Best place to ask is here: Issues · DuendeSoftware/Support · GitHub[^]

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900