I need to validate a Google ID token passed from a mobile device at my ASP.NET web api.
Google have some sample code here but it relies on a JWT NuGet package which is .Net 4.5 only (I am using C#/.Net 4.0). Is anyone aware of any samples which do this without these packages or has achieved this themselves? The use of the package makes it very difficult to work out what I need to do without it.
According to this github issue, you can now use GoogleJsonWebSignature.ValidateAsync method to validate a Google-signed JWT. Simply pass the idToken string to the method.
var validPayload = await GoogleJsonWebSignature.ValidateAsync(idToken);
Assert.NotNull(validPayload);
If it is not a valid one, it will return null.
Note that to use this method, you need to install Google.Apis.Auth nuget firsthand.
The challenge is validating the JWT certificate in the ID token. There is currently not a library I'm aware of that can do this that doesn't require .Net 4.5 and until there is a solution for JWT validation in .NET 4.0, there will not be an easy solution.
However, if you have an access token, you can look into performing validation using oauth2.tokeninfo. To perform basic validation using token info, you can do something like the following:
// Use Tokeninfo to validate the user and the client.
var tokeninfo_request = new Oauth2Service().Tokeninfo();
tokeninfo_request.Access_token = _authState.AccessToken;
var tokeninfo = tokeninfo_request.Fetch();
if (userid == tokeninfo.User_id
&& tokeninfo.Issued_to == CLIENT_ID)
{
// Basic validation succeeded
}
else
{
// The credentials did not match.
}
The information returned from the Google OAuth2 API tells you more information about a particular token such as the client id it was issued too as well as its expiration time.
Note You should not be passing around the access token but instead should be doing this check after exchanging a one-time code to retrieve an access token.
ClientId also needs to be passed, which should be set from Google API Console. If only pass TokenId, GoogleJsonWebSignature throws error. This answer is in addition to #edmundpie answer
var settings = new GoogleJsonWebSignature.ValidationSettings()
{
Audience = new List<string>() { "[Placeholder for Client Id].apps.googleusercontent.com" }
};
var validPayload = await GoogleJsonWebSignature.ValidateAsync(model.ExternalTokenId, settings);
Related
I am using this github project https://github.com/openiddict/openiddict-core which is great. But I am stuck as to what the procedures should be, or how to implement them, when the user uses an external identity provider, for this example, I will use google.
I have an angular2 app running, with an aspnet core webAPI. All my local logins work perfectly, I call connect/token with a username and password, and an accessToken is returned.
Now I need to implement google as an external identity provider. I have followed all the steps here to implement a google login button. This opens a popup when the user logins in. This is the code I have created for my google button.
// Angular hook that allows for interaction with elements inserted by the
// rendering of a view.
ngAfterViewInit() {
// check if the google client id is in the pages meta tags
if (document.querySelector("meta[name='google-signin-client_id']")) {
// Converts the Google login button stub to an actual button.
gapi.signin2.render(
'google-login-button',
{
"onSuccess": this.onGoogleLoginSuccess,
"scope": "profile",
"theme": "dark"
});
}
}
onGoogleLoginSuccess(loggedInUser) {
let idToken = loggedInUser.getAuthResponse().id_token;
// here i can pass the idToken up to my server and validate it
}
Now I have an idToken from google. The next step on the google pages found here says that I need to validate the google accessToken, which I can do, but how do I exchange the accessToken that I have from google, and create local accessToken which can be used on my application?
Edit: this answer was updated to use OpenIddict 3.x.
The next step on the google pages found here says that i need to validate the google accessToken, which i can do, but how do i exchange the accessToken that i have from google, and create local accessToken which can be used on my application?
The flow you're trying to implement is known as assertion grant. You can read this other SO post for more information about it.
OpenIddict fully supports custom grants, so this is something you can easily implement in your token endpoint action:
[HttpPost("~/connect/token"), Produces("application/json")]
public IActionResult Exchange()
{
var request = HttpContext.GetOpenIddictServerRequest();
if (request.GrantType == "urn:ietf:params:oauth:grant-type:google_identity_token")
{
// Reject the request if the "assertion" parameter is missing.
if (string.IsNullOrEmpty(request.Assertion))
{
return Forbid(
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
properties: new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
"The mandatory 'assertion' parameter was missing."
}));
}
// Create a new ClaimsIdentity containing the claims that
// will be used to create an id_token and/or an access token.
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType);
// Manually validate the identity token issued by Google, including the
// issuer, the signature and the audience. Then, copy the claims you need
// to the "identity" instance and call SetDestinations on each claim to
// allow them to be persisted to either access or identity tokens (or both).
//
// Note: the identity MUST contain a "sub" claim containing the user ID.
var principal = new ClaimsPrincipal(identity);
foreach (var claim in principal.Claims)
{
claim.SetDestinations(claim.Type switch
{
"name" => new[]
{
Destinations.AccessToken,
Destinations.IdentityToken
},
_ => new[] { Destinations.AccessToken },
});
}
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
}
throw new InvalidOperationException("The specified grant type is not supported.");
}
Note that you'll also have to enable it in the OpenIddict server options:
services.AddOpenIddict()
// ...
.AddServer(options =>
{
// ...
options.AllowCustomFlow("urn:ietf:params:oauth:grant-type:google_identity_token");
});
When sending a token request, make sure to use the right grant_type and to send your id_token as the assertion parameter, and it should work. Here's an example with Postman (for Facebook access tokens, but it works exactly the same way):
That said, you have to be extremely careful when implementing the token validation routine, as this step is particularly error-prone. It's really important to validate everything, including the audience (otherwise, your server would be vulnerable to confused deputy attacks).
In my asp.net MVC web-site, I have implemented SSO in which the IDP/ADFS sends the SAML response and I verify the SAML token to allow users to access the web-site. I am using customized code (using System.IdentityModel.dll and System.IdentityModel.Services.dll libraries to verify the SAML response and token, instead of having.net framework do the checking with web.config settings).
So far the code seems to be working fine, but since I am new to this domain have concern that hacker would able to bypass the validation with properly constructed SAML response. Recently I tried my hands on the SAML token generation part and I realized that my token verification code can be bypassed if the token is generated intelligently.
At high level this is what I am doing to verify the token:
Extract the security token from the request.
Verify the token is valid and untouched by checking the digest value with the provided X509 public key(which is present in the SAML response)
Extract the claims/identity of the user and allow the access.
My concern is that if hacker creates SAML token (like my own token generator code) and add public key in the response and post it to my web site, my web site will successfully validate the response as the response itself is well formed and signed. Is this a valid concern? I am missing some basic validations to handle this scenario?
I can think of following options to mitigate the risk:
Check the URLReferrer and make sure that the SAML response is posted from the expected entity. I am not sure if there is a way to manipulate the URLReferrer.
Avoid using the public key present in the response to validate the digest value. I can store the X509 certificate on my end and use it to validate the response. The hacker won’t able to sign the response with the same certificate as he won’t have the private key. If this is the right way, can someone suggest me how to instruct “tokenhandler” to ignore the public key present in the response and use explicit public key?
Is there a way by which I can make back-end call to IDP, May a web-service call, and check that the token received by my web site is indeed generated by the IDP?
Since I am a newbie, I might be missing the basic SAML validation concept. So please let me know if my concern is legitimate.
Here is the sample code I use to validate the response.
public ActionResult SAMLAssert()
{
var fam = new WSFederationAuthenticationModule();
var request = this.HttpContext.Request;
// Get the security token from the SAML response
var securityToken = fam.GetSecurityToken(request);
var config = new SecurityTokenHandlerConfiguration
{
CertificateValidator = X509CertificateValidator.None,
IssuerNameRegistry = new CustomIssuerNameRegistry(),
};
config.AudienceRestriction.AudienceMode = AudienceUriMode.Never;
var tokenHandler = new Saml2SecurityTokenHandler
{
CertificateValidator = X509CertificateValidator.None,
Configuration = config,
};
//// validate the token and get the ClaimsIdentity out of it
var identity = tokenHandler.ValidateToken(securityToken);
bool isSuccess = identity[0].IsAuthenticated;
// Code to retrieve the claims/user information from the token
//....
return View();
}
And here is the custom "IssuerNameRegistry".
public class CustomIssuerNameRegistry : IssuerNameRegistry
{
public override string GetIssuerName(SecurityToken securityToken)
{
X509SecurityToken x509Token = securityToken as X509SecurityToken;
return x509Token.Certificate.Subject;
}
}
I have suspicion that the custom class is the problematic part as it’s not doing any validation.
I don't think you should check the referrer value. It can easily be spoofed.
The IdP uses its private key to sign responses sending to you. An attacker just doesn't have access to this private key. Therefore, if an attacker wants to spoof a signature, he will need to use his own certificate and put his public key to the token. While you are right that validation code uses the embedded public key for verifying signature, AFAICT it also does one more thing: check if the public key is trusted by our machine. The trust here can be established by adding the public key to your Windows Certificate store -> TrustedPeople. I don't have all the code to verify this though but it should work this way, or it should provide you a way to do that.
If you have full control over the IdP, alternative to embedded public key (aka X509Data), you can use keyname, subject name, thumbprint only. But how much more secure they provide and how to implement are out of the scope of this question.
No, SAML protocol wasn't designed to work that way. The closest thing to this is to use Artifact flow in which the IdP only returns an Artifact to your application and it needs to make an ArtifactResolve request to the IdP to get the actual Response. See What is the purpose of a SAML Artifact?. But then you still need to verify signature of the received Response.
I'm have working two separate implementations of Oauth2 for both the gData and the Drive C# APIs, storing token information in an OAuth2Parameters and AuthorizationState respectively. I'm able to refresh the token and use them for the necessary API calls. I'm looking for a way to use this to get the user's information, mainly the email address or domain.
I tried following the demo for Retrieve OAuth 2.0 Credentials but I'm getting a compile error similar to rapsalands' issue here, saying it
can't convert from
'Google.Apis.Authentication.OAuth2.OAuth2Authenticator<
Google.Apis.Authentication.OAuth2.DotNetOpenAuth.NativeApplicationClient>'
to 'Google.Apis.Services.BaseClientService.Initializer'.
I just grabbed the most recent version of the Oauth2 api dlls so I don't think that's it.
All the other code samples I'm seeing around mention using the UserInfo API, but I can't find any kind of C#/dotnet api that I can use with it without simply doing straight GET/POST requests.
Is there a way to get this info using the tokens I already have with one of the C# apis without making a new HTTP request?
You need to use Oauth2Service to retrieve information about the user.
Oauth2Service userInfoService = new Oauth2Service(credentials);
Userinfo userInfo = userInfoService.Userinfo.Get().Fetch();
Oauth2Service is available on the following library: https://code.google.com/p/google-api-dotnet-client/wiki/APIs#Google_OAuth2_API
For #user990635's question above. Though the question is a little dated, the following may help someone. The code uses Google.Apis.Auth.OAuth2 version
var credentials =
await GoogleWebAuthorizationBroker.AuthorizeAsync(
new ClientSecrets {ClientId = clientID, ClientSecret = clientSecret},
new[] {"openid", "email"}, "user", CancellationToken.None);
if (credentials != null)
{
var oauthSerivce =
new Oauth2Service(new BaseClientService.Initializer {HttpClientInitializer = credentials});
UserInfo = await oauthSerivce.Userinfo.Get().ExecuteAsync();
}
I have worked with OAuth before (working with Twitter and PHP) and it was simple. I am trying to get OAuth to work with the EverNote API sample https://github.com/evernote/evernote-sdk-csharp (because, as they say, "Real applications authenticate with Evernote using OAuth"). I looked at these:
Simple C# Evernote API OAuth example or guide?
https://github.com/sethhitch/csharp-oauth-sample
http://blog.stevienova.com/2008/04/19/oauth-getting-started-with-oauth-in-c-net/
But, I still don't know how to do this... This is my code:
// Real applications authenticate with Evernote using OAuth, but for the
// purpose of exploring the API, you can get a developer token that allows
// you to access your own Evernote account. To get a developer token, visit
// https://sandbox.evernote.com/api/DeveloperToken.action
String authToken = "myAuthCode";
if (authToken == "your developer token") {
Console.WriteLine("Please fill in your developer token");
Console.WriteLine("To get a developer token, visit https://sandbox.evernote.com/api/DeveloperToken.action");
return;
}
How can I add OAuth to this to get my authToken?
Thank you.
Check this sample project : http://discussion.evernote.com/topic/30584-here-is-a-net-oauth-assembly/ . I think this will help you to understand how oauth works.
For anyone trying to get this to work in MVC, I was playing around with Evernote, OpenAuth and C# this morning and managed to get it all working. I have put together a blog post / library explaining the experience and outlining how to do it with MVC here - http://www.shaunmccarthy.com/evernote-oauth-csharp/ - it uses the AsyncOAuth library: https://github.com/neuecc/AsyncOAuth
I wrote a wrapper around AsyncOAuth that you might find useful here: https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple
One prickly thing to be aware of - the Evernote Endpoints (/oauth and /OAuth.action) are case sensitive
// Download the library from https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple
// Configure the Authorizer with the URL of the Evernote service,
// your key, and your secret.
var EvernoteAuthorizer = new EvernoteAuthorizer(
"https://sandbox.evernote.com",
"slyrp-1234", // Not my real id / secret :)
"7acafe123456badb123");
// First of all, get a request token from Evernote - this causes a
// webrequest from your server to Evernote.
// The callBackUrl is the URL you want the user to return to once
// they validate the app
var requestToken = EvernoteAuthorizer.GetRequestToken(callBackUrl);
// Persist this token, as we are going to redirect the user to
// Evernote to Authorize this app
Session["RequestToken"] = requestToken;
// Generate the Evernote URL that we will redirect the user to in
// order to
var callForwardUrl = EvernoteAuthorizer.BuildAuthorizeUrl(requestToken);
// Redirect the user (e.g. MVC)
return Redirect(callForwardUrl);
// ... Once the user authroizes the app, they get redirected to callBackUrl
// where we parse the request parameter oauth_validator and finally get
// our credentials
// null = they didn't authorize us
var credentials = EvernoteAuthorizer.ParseAccessToken(
Request.QueryString["oauth_verifier"],
Session["RequestToken"] as RequestToken);
// Example of how to use the credential with Evernote SDK
var noteStoreUrl = EvernoteCredentials.NotebookUrl;
var noteStoreTransport = new THttpClient(new Uri(noteStoreUrl));
var noteStoreProtocol = new TBinaryProtocol(noteStoreTransport);
var noteStore = new NoteStore.Client(noteStoreProtocol);
List<Notebook> notebooks = client.listNotebooks(EvernoteCredentials.AuthToken);
You can also try the OAuth library found here : https://code.google.com/p/devdefined-tools/wiki/OAuth and follow the steps mentioned here.
The simple code to add is:
EvernoteOAuth oauth = new EvernoteOAuth(EvernoteOAuth.HostService.Sandbox, myConsumerKey, myConsumerSecret);
string errResponse = oauth.Authorize();
if (errResponse.Length == 0)
{
Console.WriteLine(string.Format("Token: {0}\r\n\r\nExpires: {1}\r\n\r\nNoteStoreUrl: {2}\r\n\r\nUserId: {3}\r\n\r\nWebApiUrlPrefix: {4}", oauth.Token, oauth.Expires, oauth.NoteStoreUrl, oauth.UserId, oauth.WebApiUrlPrefix));
}
else
{
Console.WriteLine("A problem has occurred in attempting to authorize the use of your Evernote account: " + errResponse);
}
You will need to use this assembly:
using EvernoteOAuthNet;
Available here:
http://www32.zippyshare.com/v/98249023/file.html
The short question is whether is this possible and if so, how?
Outline
I have a .NET application which currently uses a service account to access information across a Google Apps domain using the Google Drive API. This works fine using the google-api-dotnet-client library and code along the same lines as shown in the samples here - which are currently a very good basic example of what I'm doing.
What I want to do now is extend it so as well as using those APIs provided by the "new" google-api-dotnet-client library, it uses the older "GData" libraries, as provided for via the
older google-gdata library, specifically the Spreadsheets API (and perhaps more to come).
The Problem
This is where the difficulty arises. The former library does exactly what I want, as evidenced by the second link in the first paragraph above - and the fact I have it doing it myself. HOWEVER... although the second library has been updated to support OAuth 2.0 in addition to OAuth 1.0 and the other older auth techniques, it does not - as far as I can tell from extensive Googling and trail-and-error - allow the "service account on behalf of all my users" operation which I need.
My question is whether I'm missing something (possibly a hard to find or undocumented something) which would allow me to do what I want. Failing that, is there any way I could force this behaviour and make these two libraries operate side by side?
The ideal solution
Ideally I would love some way of having the Google.GData.Spreadsheets.SpreadsheetsService instance be able to take advantage of the Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> instance I'm already using... somehow. Is such witchcraft possible? I'm I missing the obvious?
Failing that, I'm happy to do the whole OAuth2 "assertion flow client" dance again if I have to, in some way that the older library can handle.
Help?
Other Thoughts
I have considered - and rejected for the time being - the option of starting from scratch and writing my own library to make this happen. This is for two reasons:
The gdata library already exists, and has been developed by many people likely cleverer than myself. I'm not so arrogant that I believe I can do better.
I'm not certain the OAuth2 with service account approach is even supported/allowed on these older APIs.
An alternate approach which I've been hoping to avoid but may have to fall back to depending on the answers here will be to use 2-legged OAuth 1.0 for portions of this. I'd prefer not to, as having parts of the app rely on one old auth method whilst other parts do it the nice new way just feels wrong to me. And there's that much more to go wrong...
Updates
I have considered the possibility of subclassing GDataRequestFactory and GDataRequest so I can make my own request factory and have that take the instance of Google.Apis.Authentication.Auth2Authenticator<AssertionFlowClient> (well, an instance of Google.Apis.Authentication.IAuthenticator anyway) which could step in to authenticate the request just before it's called. However... the constructor for GDataRequest is internal, which has stopped me.
It's really looking like this isn't meant to be.
For the sake of other folks coming across this question (now that the solution linked to in the accepted answer uses deprecated code), here's how I solved it:
First, start in "new API" land (use the Google.Apis.Auth nuget package) by setting up a ServiceAccountCredential following Google's Service Account example:
//In the old api, this accessed the main api accounts' sheets, not anymore
//** Important ** share spreadsheets with the Service Account by inviting the "serviceAccountEmail" address to the sheet
string serviceAccountEmail = "12345697-abcdefghijklmnop#developer.gserviceaccount.com";
var certificate = new X509Certificate2(#"key.p12", "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" }
}.FromCertificate(certificate));
Tell the credential to request an Access Token:
credential.RequestAccessTokenAsync(System.Threading.CancellationToken.None).Wait();
Now it's time to switch back to "old API" land (use the Google.GData.Spreadsheets nuget package). Start by constructing the SpreadsheetsService (similar to Google's example):
SpreadsheetsService service = new SpreadsheetsService("MySpreadsheetIntegration-v1");
To use Service Account authentication, we'll create an instance of the GDataRequestFactory and set a custom Authorization header:
var requestFactory = new GDataRequestFactory("My App User Agent");
requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
Finally, set the SpreadsheetsService's RequestFactory property to this new factory:
service.RequestFactory = requestFactory;
And go ahead and use the SpreadsheetsService as you would had you authenticated using any other technique. (Tip: share spreadsheets with the Service Account by inviting the serviceAccountEmail address to the sheet)
I managed to solve this by subclassing GDataRequestFactory and creating my own implementation of the interfaces implemented by GDataRequest. This implementation wraps an instance of GDataRequest instantiated via reflection, and adds in the necessary code to perform authentication using an instance of IAuthenticator (in my case, Auth2Authenticator).
I wrote a blog post on it and added an example as a Gist:
Blog: Using Google's Spreadsheet API using .NET, OAuth 2.0 and a Service Account
Gist 4244834
Feel free to use this if it helps you (BSD licence).
Hey just stumbled accross the same problem and produced a different solution:
Has anybody ever concidered of writing the parameters from the credentials-object directly to an OAuth2Parameters-Object?
I did this and it worked nicely:
public class OAuthTest
{
OAuth2Parameters param = new OAuth2Parameters();
public OAuthTest()
{
Debug.WriteLine("Calling: AuthGoogleDataInterface()");
bool init = AuthGoogleDataInterface();
if (init)
{
GOAuth2RequestFactory requestFactory = new GOAuth2RequestFactory(null, "My App User Agent", this.param);
//requestFactory.CustomHeaders.Add(string.Format("Authorization: Bearer {0}", credential.Token.AccessToken));
var service = new SpreadsheetsService("MyService");
service.RequestFactory = requestFactory;
SpreadsheetQuery query = new SpreadsheetQuery();
// Make a request to the API and get all spreadsheets.
SpreadsheetFeed feed = service.Query(query);
// Iterate through all of the spreadsheets returned
foreach (SpreadsheetEntry entry in feed.Entries)
{
// Print the title of this spreadsheet to the screen
Debug.WriteLine(entry.Title.Text);
}
}
Debug.WriteLine(m_Init);
}
private bool AuthGoogleDataInterface()
{
bool b_success;
try
{
Console.WriteLine("New User Credential");
// New User Credential
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
GoogleClientSecrets GCSecrets = GoogleClientSecrets.Load(stream);
string[] ArrScope = new[] { "https://spreadsheets.google.com/feeds", "https://docs.google.com/feeds" };
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GCSecrets.Secrets,
ArrScope,
"user", CancellationToken.None,
new FileDataStore("My.cal")).Result;
// put the Information generated for the credentials object into the OAuth2Parameters-Object to access the Spreadsheets
this.param.ClientId = GCSecrets.Secrets.ClientId; //CLIENT_ID;
this.param.ClientSecret = GCSecrets.Secrets.ClientSecret; //CLIENT_SECRET;
this.param.RedirectUri = "urn:ietf:wg:oauth:2.0:oob"; //REDIRECT_URI;
this.param.Scope = ArrScope.ToString();
this.param.AccessToken = credential.Token.AccessToken;
this.param.RefreshToken = credential.Token.RefreshToken;
}
Debug.WriteLine("AuthGoogleDataInterface: Success");
b_success = true;
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
b_success = false;
}
return b_success;
}
}