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.