Google Access Token Authentication for .netcore 2.1
This extension adds Google authorization to Web Api's in dot net 2.1. It does not do a OIDC authentication, it only verifies the google tokens, and adds claims to the principal from googles tokeninfo endpoint and people endpoint. There are some options you can set to affect the behavior of the plugin and these are covered here below. There is also some caching support and will also be documented here below. There is a Google namespace in .netcore security, but as far as I can see it is used for websites to do a Google authentication. If I'm wrong feel free to point it out in the Issues.
This is by no means a best practise for providing authorization to you apis. The best way would to protect it with jwt tokens and setup a identity server and use jwt tokens from it to authorize. You could then also sign in with google and get the google access token and send it down to the api's if you need to access the google apis. This does introduce some overhead and some times you just want to throw togeater an api fast and stil want some authorization. This might be a case where this extension would be helpful. If you have anything that is not going to be just a hobby implementation of an api, go with the Identity Server route.
Using the plugin is strait forward if you are already familiar with .netcore authentication, it's setup as a authentication scheme.
Here is an simple example ( from Startup.cs )
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME;
})
.AddGoogleTokenAuthorization();
}
Here is an example using all of the supported options
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME;
})
.AddGoogleTokenAuthorization(
GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME,
GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME,
o =>
{
o.UseMemoryCache = true;
o.ClientId = "yourappid.apps.googleusercontent.com";
o.RequireVerifiedEmail = false;
o.RequireCorrectClientId = true;
o.ExpieryWindowSeconds = 20;
o.OnlyAllowOfflineTokens = true;
o.RequiredScopes = new List<string>
{
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/plus.profile.agerange.read",
"https://www.googleapis.com/auth/plus.profile.language.read"
};
}
);
}
Lets go over what the options mean ( they are all optional ).
Default is false
Uses the IMemoryCache
. It is a poor mans version of cache sinse if you have multiple servers, the cache will only work on each individual server.
If this option is set, the IAuthenticationTicketCache
will use the AuthenticationTicketMemoryCache
implementation wich is the only
implementation provided with the plugin. You can how ever provide you own.
Default is null
This is the client id of the client that provided the access token. The value from the tokeninfo aud
value must mach this value only if
RequireCorrectClientId
is set to true.
Default is false
If this option is set to true, the ClientId
must mach the tokeninfo aud
value
Default is null
If this value is null, there are no scope restrictions. If you provide scopes here, the token must provide all of these scopes to be able to access the api
Default is false
If this value is set to true, only offile ( server side tokens ) are allowed. Corresponds to access_type
from the tokeninfo endpoint
Default is 10
Expire tokens before they really expire, you can set a window where the token is considered expired althogh it stil has some time left.
Usfull if for example the token expires in one second, but there is some processing in the api and when you eventually call google,
the token is expired.
Here is an simple example on how to protect an endpoint
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Authorize(AuthenticationSchemes = GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME)]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
The included cache IAuthenticationTicketCache
uses this implementation AuthenticationTicketMemoryCache
which uses IMemoryCache
.
You can provide your own implementation of IAuthenticationTicketCache
and bind it to the DI but to be honest if you need it,
you have propably outgrown this extension and should propably be using Identity server instead.
Is out of scope here to talk about policies but if you are new to netcore authentication/authorization ill add a simple example here how to create a policy that checks for an spesific email
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME;
})
.AddGoogleTokenAuthorization(
GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME,
GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME,
o =>
{
o.UseMemoryCache = true;
o.ClientId = "yourappid.apps.googleusercontent.com";
o.RequireVerifiedEmail = false;
o.RequireCorrectClientId = true;
o.ExpieryWindowSeconds = 20;
o.OnlyAllowOfflineTokens = true;
o.RequiredScopes = new List<string>
{
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/plus.profile.agerange.read",
"https://www.googleapis.com/auth/plus.profile.language.read"
};
}
);
services.AddAuthorization(o =>
{
o.AddPolicy("IsSpecificEmail", policy =>
policy.RequireAssertion(context =>
context.User.HasClaim(c => (c.Type == "email" && c.Value == "youremail@gmail.com"))
)
);
});
}
}
Now we have set up a policy that will only allow youremail@gmail.com
to call the endpoint, lets add it to the endpoint
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "IsSpecificEmail", AuthenticationSchemes = GoogleAccessTokenDefaults.GOOGLE_TOKEN_SCHEME)]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
}
For more information take a look at the documentation here
There are alot of claims set on the identity with this extension. They are obtained from google's tokeninfo and people endpoints I will just list theme here
Note that the suffix indicades that there could be more than one and each entry will have a number counting from one.
azp
aud
sub
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
exp
Value of this is DateTimeOffset.Now.ToUnixTimeSeconds() plus the expiry seconds from the tokeninfo endpoint minus the options.ExpieryWindowSeconds
exp_epoc
Value of this is DateTimeOffset.Now.ToUnixTimeSeconds() plus the expiry seconds from the tokeninfo endpoint minus the options.ExpieryWindowSeconds
http://schemas.microsoft.com/ws/2008/06/identity/claims/expiration
Value of this is DateTimeOffset.Now.ToUnixTimeSeconds() plus the expiry seconds from the tokeninfo endpoint minus the options.ExpieryWindowSeconds
access_type
email
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
email_verified
scope
Value of this is a space seperated scope list
id
kind
etag
objectType
displayName
url
isPlusUser
language
circledByCount
nickname
occupation
skills
birthday
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth
gender
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender
tagline
braggingRights
aboutMe
relationshipStatus
plusOneCount
verified
domain
email<NUMBER>
email<NUBMER>.type
url<NUMBER>
url<NUMBER>.type
url<NUMBER>.label
name
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
familyName
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
givenName
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
middleName
honorificPrefix
honorificSuffix
image.url
image.isDefault
organizations<NUMBER>.department
organizations<NUMBER>.description
organizations<NUMBER>.endDate
organizations<NUMBER>.location
organizations<NUMBER>.name
organizations<NUMBER>.primary
organizations<NUMBER>.startDate
organizations<NUMBER>.title
organizations<NUMBER>.type
placesLived<NUMBER>
placesLived<NUMBER>.primary
cover.layout
cover.coverInfo.leftImageOffset
cover.coverInfo.topImageOffset
cover.coverPhoto.height
cover.coverPhoto.width
cover.coverPhoto.url