This post is a continuation of my exploration of using Auth0 with ASP.NET Core with an API and an Angular front end. I recommend you start with the first post if you are new to Auth0.
This post is going to add a login from Angular in the Client Application as well as accessing the API once logged in. The starting point of the code can be found here.
API Application
To give us an endpoint in the API to call let’s move theĀ SimpleDataControllerĀ class from theĀ ClientApp/ControllersĀ directory toĀ ApiApp/ControllersĀ directory. Also, remember to adjust the namespace to reflect this move.
To remove some complication we are going to add a CORS policy to the API Application to allow all CORS request. This wouldn’t necessarily something I would recommend for a production application. For more information on CORS check out this post.
To add the CORS policy open theĀ StartupĀ class and add the following to theĀ ConfigureServicesĀ function which adds a CORS policy to DI that allows all calls through.
services.AddCors(options =>
{
options.AddPolicy("default", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Next, in theĀ ConfigureĀ function, CORS needs to be added to the HTTP pipeline with the policy added above. The following is the full function body for reference, but only the CORS line is new.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("default");
app.UseAuthentication();
app.UseMvc();
Note that the API isn’t currently using Authorization at all and won’t be for this post.
Client Application
Instead of redoing a lot of the same work that I covered in some of my posts on using Identity Server and Angular together I decided to copy theĀ ClientAppĀ directory and use it as a starting point. The rest of the client changes are going to assume the same starting point which means we will be usingĀ angular-auth-oidc-clientĀ instead of the Auth0 client to do the Open ID Connect bits.
Configuration values from appsettings.json
We need some setting from the ASP.NET Core part of the client application. First, add the following new settings to theĀ appsettings.jsonĀ file.
"ApiAddress": "http://localhost:50467/api/",
"ClientId": "yourAngularClientId"
Next,Ā Index.cshtmlĀ needs to be changed to provide some prerender data. The following shows the new data being passed into the Angular application (this post covers this in detail).
<app asp-prerender-module="ClientApp/dist/main-server"
asp-prerender-data='new {
apiUrl = Configuration["ApiAddress"],
identityUrl = $"https://{Configuration["Auth0:Domain"]}",
clientId = Configuration["ClientId"]
}'>Loading...</app>
Now that the prerender values are being passed in we need to handle them in theĀ boot.server.tsĀ file in theĀ createServerRendererĀ export. The following is the full list of values.
export default createServerRenderer(params => {
const providers = [
{ provide: INITIAL_CONFIG, useValue: { document: '<app></app>', url: params.url } },
{ provide: APP_BASE_HREF, useValue: params.baseUrl },
{ provide: 'BASE_URL', useValue: params.origin + params.baseUrl },
{ provide: 'ORIGIN_URL', useValue: params.origin + params.baseUrl },
{ provide: 'API_URL', useValue: params.data.apiUrl },
{ provide: 'IDENTITY_URL', useValue: params.data.identityUrl },
{ provide: 'CLIENT_ID', useValue: params.data.clientId },
{ provide: 'URL_CONFIG', useValue: params.data}
];
Down a bit in the file, theĀ setImmediateĀ call needs to be changed to the following.
setImmediate(() => {
resolve({
html: state.renderToString(),
globals: {url_Config: params.data}
});
Next, in theĀ app.module.browser.tsĀ file weĀ need functions for getting the config values as well as the associatedĀ providers. The following is the full file without the imports.
@NgModule({
bootstrap: [AppComponent],
imports: [
BrowserModule,
AppModuleShared
],
providers: [
{ provide: 'ORIGIN_URL', useFactory: getBaseUrl },
{ provide: 'API_URL', useFactory: apiUrlFactory },
{ provide: 'IDENTITY_URL', useFactory: identityUrlFactory },
{ provide: 'CLIENT_ID', useFactory: clientIdFactory },
AppModuleShared
]
})
export class AppModule {
}
export function getBaseUrl() {
return document.getElementsByTagName('base')[0].href;
}
export function apiUrlFactory() {
return (window as any).url_Config.apiUrl;
}
export function identityUrlFactory() {
return (window as any).url_Config.identityUrl;
}
export function clientIdFactory() {
return (window as any).url_Config.clientId;
}
Finally, in theĀ auth.service.tsĀ file weĀ need to inject the new configuration values. The following is the constructor that takes in the new values as well as uses them in the set upĀ of theĀ OpenIDImplicitFlowConfiguration.
constructor(public oidcSecurityService: OidcSecurityService,
private http: HttpClient,
@Inject('ORIGIN_URL') originUrl: string,
@Inject('IDENTITY_URL') identityUrl: string,
@Inject('CLIENT_ID') clientId: string
) {
const openIdImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration();
openIdImplicitFlowConfiguration.stsServer = identityUrl;
openIdImplicitFlowConfiguration.redirect_url = originUrl + 'callback';
openIdImplicitFlowConfiguration.client_id = clientId;
openIdImplicitFlowConfiguration.response_type = 'id_token token';
openIdImplicitFlowConfiguration.scope = 'openid profile apiApp';
openIdImplicitFlowConfiguration.post_logout_redirect_uri = originUrl + 'home';
openIdImplicitFlowConfiguration.forbidden_route = '/forbidden';
openIdImplicitFlowConfiguration.unauthorized_route = '/unauthorized';
openIdImplicitFlowConfiguration.auto_userinfo = true;
openIdImplicitFlowConfiguration.log_console_warning_active = true;
openIdImplicitFlowConfiguration.log_console_debug_active = false;
openIdImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 10;
this.oidcSecurityService.setupModule(openIdImplicitFlowConfiguration);
if (this.oidcSecurityService.moduleSetup) {
this.doCallbackLogicIfRequired();
} else {
this.oidcSecurityService.onModuleSetup.subscribe(() => {
this.doCallbackLogicIfRequired();
});
}
}
Make sure in the Auth0 client setup you allow the callback listed above or you will run into issues.
Wrapping Up
Working on this post furtherĀ convinced me that Open ID Connect is the way to go. I basically took an implementation that was meant for Identity Server and used it with Auth0.
Make note that the reason the API is unsecured at this point is that I had an issue gettingĀ angular-auth-oidc-clientĀ to play nicely with Auth0 for some reason. I doubt it is an issue with either product just some sort of missing configuration on my part. This is the part of the post that I sunk so much time on. In the end, I decided to just skip it for now. If anyone does get that setup work I would love to see the sample so I know what I was doing wrong.
The completed code can be found here.