There are many Angular tutorials for setting up websites using the Angular framework and .NET Core APIs. Likewise, there are many walkthroughs for integrating Google authentication with each. However, implementing these solutions separately yield the need to authenticate through Google twice, once for the angular site, once for the API.
This article provides a solution that allows shared Google authorization through authentication on the angular site. To surpass the need to authenticate a second time, pass the token through a standard header to the API and use Google libraries to validate and authorize.
Technology used in this Angular tutorial.
- Angular 9 website
- angular-social-login library (obsolete)
- .Net Core 3 Web API
- Google.Apis.Auth nuget package
This post assumes you’ve got the basic angular website and Web API projects running. This post will also likely be effective for any angular site 2+ or front end site where google authentication occurs. It should also work if your Web API project is Core 2+.
The site I’m working with is designed to be exclusively authenticated through Google, however this method could be extended to handle multiple authentication formats (assuming there are .Net validation libraries for them or you write your own). Therefore, one other aspect to mention is that I am not storing any user data in a database.
Using the Angular site, Google login, and local storage as a start.
The primary goal is to make sure you have access to Google’s idToken after authentication. Using the angular-social-login default setup is pretty simple to get working. This is a pretty good article which also walks through setting up the Google App as part of this if you need. I can’t find the original post I followed, but this stackoverflow post shows storing the Google user/token in state for future calls.
This code block (customauth.service.ts in the Angular site) just shows that on user subscription the user is stored in local storage:
constructor(
public authService: AuthService,
public router: Router,
public ngZone: NgZone // NgZone service to remove outside scope warning
) {
// Setting logged in user in localstorage else null
this.authService.authState.subscribe(user => {
if (user) {
this.userData = user;
localStorage.setItem('user', JSON.stringify(this.userData));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
// Sign in with Google
GoogleAuth() {
return this.authService.signIn(GoogleLoginProvider.PROVIDER_ID);
}
// Sign out
SignOut() {
return this.authService.signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['/']);
});
}
Options researched before finding the current solution.
- The Microsoft standard way to handle google authentication. This is slick if you’re building an MVC site and need to allow Google auth, but I couldn’t find a way to allow sending over the token, as this generates and uses a cookie value with a Identity.External key.
- JWT authorization is an option, but the tutorials got heavy quickly. Since I don’t need to store users or use Microsoft Identity, I blew past this.
- A custom policy provider is another Microsft standard practice. There might be a better way to accomplish the solution using this approach, but I didn’t walk this path too far since I wasn’t using authentication through the .Net solution.
The solution: a .Net Core custom authorize attribute.
I used this stackoverflow post about custom auth attributes to hook up the solution. This is what allows the shared Google authorization using a standard authorization request header.
Approach
- In Angular
- Build the Authorization header using the Google idToken.
- Pass the header for any authorize only API endpoints.
- In the web API
- Enable authorization
- Create a custom IAuthorizationFilter and TypeFilterAttribute
- Tag any controllers or endpoints with the custom attribute
I provide code samples for these steps below.
Angular API calls with an authorization header.
The code in the api service (api.service.ts in Angular Site) grabs the id token from the user in local storage and passes it through the API call. If the user is logged out, this header isn’t passed.
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { SocialUser } from 'angularx-social-login';
import { environment } from './../../environments/environment';
export class ApiService {
apiURL = environment.apiUrl;
user: SocialUser;
defaultHeaders: HttpHeaders;
constructor(private httpClient: HttpClient) {
this.user = JSON.parse(localStorage.getItem('user'));
this.defaultHeaders = new HttpHeaders();
this.defaultHeaders = this.defaultHeaders.append('Content-Type', 'application/json');
if (this.user != null) {
this.defaultHeaders = this.defaultHeaders.append('Authorization', 'Bearer ' + this.user.idToken);
}
}
public getAccounts() {
const accounts = this.httpClient.get<Account[]>(`${this.apiURL}/accounts`, { headers: this.defaultHeaders });
return accounts;
}
}
Enabling authorization in the .Net Core project.
In the StartUp file (StartUp.cs in the API project), authorization has to be enabled.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The custom filter attribute to validate without another authorization.
This creates the attribute used for authorization and performs a Google validation on the token.
This application is used only for our Google G Suite users, and thus the “HostedDomain” option of the ValidationSettings is set. This isn’t necessary, and I believe can just be removed if you allow any Google user to authenticate.
I’ve named this file GoogleAuthorizationFilter.cs in the API project.
using Google.Apis.Auth;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
namespace YourNamespace.API.Attributes
{
/// <summary>
/// Custom Google Authentication authorize attribute which validates the bearer token.
/// </summary>
public class GoogleAuthorizeAttribute : TypeFilterAttribute
{
public GoogleAuthorizeAttribute() : base(typeof(GoogleAuthorizeFilter)) { }
}
public class GoogleAuthorizeFilter : IAuthorizationFilter
{
public GoogleAuthorizeFilter()
{
}
public void OnAuthorization(AuthorizationFilterContext context)
{
try
{
// Verify Authorization header exists
var headers = context.HttpContext.Request.Headers;
if (!headers.ContainsKey("Authorization"))
{
context.Result = new ForbidResult();
}
var authHeader = headers["Authorization"].ToString();
// Verify authorization header starts with bearer and has a token
if (!authHeader.StartsWith("Bearer ") && authHeader.Length > 7)
{
context.Result = new ForbidResult();
}
// Grab the token and verify through google. If verification fails, and exception will be thrown.
var token = authHeader.Remove(0, 7);
var validated = GoogleJsonWebSignature.ValidateAsync(token, new GoogleJsonWebSignature.ValidationSettings()
{
HostedDomain = "yourdomain.com",
}).Result;
}
catch (Exception)
{
context.Result = new ForbidResult();
}
}
}
}
Putting the custom attribute in place.
This is just a snippet of code, as on your controllers you just have to add the one line of code (well, two including the using statement). If the GoogleAuthorize doesn’t validate, the call returns as access denied.
using YourNamespace.API.Attributes;
[GoogleAuthorize]
[ApiController]
public class AccountsController : BaseController {
Voila! No need for a second authentication.
The .Net API is now locked down only to requests originating from a site with Google authentication. The custom attribute can be extended for additional authentication sources or any other desired restrictions using the request. I like the simplicity of a site which allows Google auth only, but it wouldn’t be a stretch to add others – and I really like not managing any users or passwords. I hope this Angular tutorial for shared Google authentication works well for you too!