This post shows how ASP.NET Core application settings can be used to configure an Angular application. ASP.NET Core provides excellent support for different configuration per environment, and so using this for an Angular application can be very useful. Using CI, one release build can be automatically created with different configurations, instead of different release builds per deployment target.
Code: https://github.com/damienbod/AspNet5IdentityServerAngularImplicitFlow/tree/master/src/AngularClient
History
2017-11-05 Updated to Angular 5 and Typescript 2.6.1
ASP.NET Core Hosting application
The ClientAppSettings class is used to load the strongly typed appsettings.json from the json file. The class contains the properties required for OIDC configuration in the SPA and the required API URLs. These properties have different values per deployment, so we do not want to add these in a typescript file, or change with each build.
namespace AngularClient.ViewModel { public class ClientAppSettings { public string stsServer { get; set; } public string redirect_url { get; set; } public string client_id { get; set; } public string response_type { get; set; } public string scope { get; set; } public string post_logout_redirect_uri { get; set; } public bool start_checksession { get; set; } public bool silent_renew { get; set; } public string startup_route { get; set; } public string forbidden_route { get; set; } public string unauthorized_route { get; set; } public bool log_console_warning_active { get; set; } public bool log_console_debug_active { get; set; } public string max_id_token_iat_offset_allowed_in_seconds { get; set; } public string apiServer { get; set; } public string apiFileServer { get; set; } } }
The appsettings.json file contains the actual values which will be used for each different environment.
{ "ClientAppSettings": { "stsServer": "https://localhost:44318", "redirect_url": "https://localhost:44311", "client_id": "angularclient", "response_type": "id_token token", "scope": "dataEventRecords securedFiles openid profile", "post_logout_redirect_uri": "https://localhost:44311", "start_checksession": false, "silent_renew": false, "startup_route": "/dataeventrecords", "forbidden_route": "/forbidden", "unauthorized_route": "/unauthorized", "log_console_warning_active": true, "log_console_debug_active": true, "max_id_token_iat_offset_allowed_in_seconds": 10, "apiServer": "https://localhost:44390/", "apiFileServer": "https://localhost:44378/" } }
The ClientAppSettings class is then added to the IoC in the ASP.NET Core Startup class and the ClientAppSettings section is used to fill the instance with data.
public void ConfigureServices(IServiceCollection services) { services.Configure<ClientAppSettings>(Configuration.GetSection("ClientAppSettings")); services.AddMvc();
A MVC Controller is used to make the settings public. This class gets the strongly typed settings from the IoC and returns it in a HTTP GET request. No application secrets should be included in this HTTP GET request!
using AngularClient.ViewModel; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace AngularClient.Controllers { [Route("api/[controller]")] public class ClientAppSettingsController : Controller { private readonly ClientAppSettings _clientAppSettings; public ClientAppSettingsController(IOptions<ClientAppSettings> clientAppSettings) { _clientAppSettings = clientAppSettings.Value; } [HttpGet] public IActionResult Get() { return Ok(_clientAppSettings); } } }
Configuring the Angular application
The Angular application needs to read the settings and use these in the client application. A configClient function is used to GET the data from the server. The APP_INITIALIZER could also be used, but as the settings are been used in the main AppModule, you still have to wait for the HTTP GET request to complete.
configClient() { // console.log('window.location', window.location); // console.log('window.location.href', window.location.href); // console.log('window.location.origin', window.location.origin); return this.http.get(window.location.origin + window.location.pathname + '/api/ClientAppSettings').map(res => { this.clientConfiguration = res.json(); }); }
In the constructor of the AppModule, the module subscribes to the configClient function. Here the configuration values are read and the properties are set as required for the SPA application.
clientConfiguration: any; constructor(public oidcSecurityService: OidcSecurityService, private http: Http, private configuration: Configuration) { console.log('APP STARTING'); this.configClient().subscribe(config => { let openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration(); openIDImplicitFlowConfiguration.stsServer = this.clientConfiguration.stsServer; openIDImplicitFlowConfiguration.redirect_url = this.clientConfiguration.redirect_url; openIDImplicitFlowConfiguration.client_id = this.clientConfiguration.client_id; openIDImplicitFlowConfiguration.response_type = this.clientConfiguration.response_type; openIDImplicitFlowConfiguration.scope = this.clientConfiguration.scope; openIDImplicitFlowConfiguration.post_logout_redirect_uri = this.clientConfiguration.post_logout_redirect_uri; openIDImplicitFlowConfiguration.start_checksession = this.clientConfiguration.start_checksession; openIDImplicitFlowConfiguration.silent_renew = this.clientConfiguration.silent_renew; openIDImplicitFlowConfiguration.startup_route = this.clientConfiguration.startup_route; openIDImplicitFlowConfiguration.forbidden_route = this.clientConfiguration.forbidden_route; openIDImplicitFlowConfiguration.unauthorized_route = this.clientConfiguration.unauthorized_route; openIDImplicitFlowConfiguration.log_console_warning_active = this.clientConfiguration.log_console_warning_active; openIDImplicitFlowConfiguration.log_console_debug_active = this.clientConfiguration.log_console_debug_active; openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = this.clientConfiguration.max_id_token_iat_offset_allowed_in_seconds; this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration); configuration.FileServer = this.clientConfiguration.apiFileServer; configuration.Server = this.clientConfiguration.apiServer; }); }
The Configuration class can then be used throughout the SPA application.
import { Injectable } from '@angular/core'; @Injectable() export class Configuration { public Server = 'read from app settings'; public FileServer = 'read from app settings'; }
I am certain, there is a better way to do the Angular configuration, but not much information exists for this. APP_INITIALIZER is not so well documentated. Angular CLI has it’s own solution, but the configuration file cannot be read per environment.
Links:
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments
https://www.intertech.com/Blog/deploying-angular-4-apps-with-environment-specific-info/
https://stackoverflow.com/questions/43193049/app-settings-the-angular-4-way
[…] Angular Configuration using ASP.NET Core settings (Damien Bowden) […]
Full of security holes. Effectively leaking server configuration right through, regardless of whether you include “secure” settings or not. A hackers dream.
Hi I don’t see what security is at risk here. All the properties are public knowlegde, which ones are dangerous?
Greetings Damien
Client side settings should NOT be sensitive information anyway…. what’s your point? If you think your settings are secure in a client app config file, you are doing it wrong
Why people used to criticize without making any proposal? If you think that is wrong, then propose your “awesome” solution. Cheers.
[…] Google Chrome Developers The Ultimate Angular CLI Reference Guide – Jurgen Van de Moere Angular Configuration using ASP.NET Core settings – Damien Bowden TypeScript: JavaScript + Types = Awesome Developer Productivity – […]