/// <summary> /// Verifies the token against the client ID. /// </summary> /// <param name="authState">The credential to verify.</param> /// <returns>The user ID that is associated with this token.</returns> static public String VerifyToken(IAuthorizationState authState) { // 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 (tokeninfo == null) { throw new TokenVerificationException("Error while fetching tokeninfo"); } // Verify the first part of the token for authorization. On mobile clients, // the full string might differ, but the first part is consistent. Regex matcher = new Regex("(\\d+)(.*).apps.googleusercontent.com$"); if (matcher.Match(tokeninfo.Issued_to).Groups[1].Value != matcher.Match(CLIENT_ID).Groups[1].Value) { throw new TokenVerificationException("Issuer other than current client."); } return(tokeninfo.User_id); }
/// <summary> /// Verifies the token against the client ID. /// </summary> /// <param name="authState">The credential to verify.</param> /// <returns>The user ID that is associated with this token.</returns> public static String VerifyToken(IAuthorizationState authState) { // 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 (tokeninfo == null) { throw new TokenVerificationException("Error while fetching tokeninfo"); } // Verify the first part of the token for authorization. On mobile clients, // the full string might differ, but the first part is consistent. Regex matcher = new Regex("(\\d+)(.*).apps.googleusercontent.com$"); if (matcher.Match(tokeninfo.Issued_to).Groups[1].Value != matcher.Match(CLIENT_ID).Groups[1].Value) { throw new TokenVerificationException("Issuer other than current client."); } return tokeninfo.User_id; }
/// <summary> /// Processes the request based on the path. /// </summary> /// <param name="context">Contains the request and response.</param> public void ProcessRequest(HttpContext context) { // Redirect base path to signin. if (context.Request.Path.Equals("/")) { context.Response.RedirectPermanent("signin.ashx"); } // This is reached when the root document is passed. Return HTML // using index.html as a template. if (context.Request.Path.Equals("/signin.ashx")) { String state = (String)context.Session["state"]; // Store a random string in the session for verifying // the responses in our OAuth2 flow. if (state == null) { Random random = new Random((int)DateTime.Now.Ticks); StringBuilder builder = new StringBuilder(); for (int i = 0; i < 13; i++) { builder.Append(Convert.ToChar( Convert.ToInt32(Math.Floor( 26 * random.NextDouble() + 65)))); } state = builder.ToString(); context.Session["state"] = state; } // Render the templated HTML. String templatedHTML = File.ReadAllText( context.Server.MapPath("index.html")); templatedHTML = Regex.Replace(templatedHTML, "[{]{2}\\s*APPLICATION_NAME\\s*[}]{2}", APP_NAME); templatedHTML = Regex.Replace(templatedHTML, "[{]{2}\\s*CLIENT_ID\\s*[}]{2}", CLIENT_ID); templatedHTML = Regex.Replace(templatedHTML, "[{]{2}\\s*STATE\\s*[}]{2}", state); context.Response.ContentType = "text/html"; context.Response.Write(templatedHTML); return; } if (context.Session["authState"] == null) { // The connect action exchanges a code from the sign-in button, // verifies it, and creates OAuth2 credentials. if (context.Request.Path.Contains("/connect")) { // Get the code from the request POST body. StreamReader sr = new StreamReader( context.Request.InputStream); string code = sr.ReadToEnd(); string state = context.Request["state"]; string userid = context.Request["gplus_id"]; // Test that the request state matches the session state. if (!state.Equals(context.Session["state"])) { context.Response.StatusCode = 401; return; } // Manually perform the OAuth2 flow for now. var authObject = ManualCodeExchanger.ExchangeCode(code); // Create an authorization state from the returned token. _authState = CreateState( authObject.access_token, authObject.refresh_token, DateTime.UtcNow, DateTime.UtcNow.AddSeconds(authObject.expires_in)); // 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) { context.Session["authState"] = _authState; } else { // The credentials did not match. context.Response.StatusCode = 401; return; } } else { // No cached state and we are not connecting. context.Response.StatusCode = 400; return; } } else { // Register the authenticator and construct the Plus service // for performing API calls on behalf of the user. _authState = (IAuthorizationState)context.Session["authState"]; AuthorizationServerDescription description = GoogleAuthenticationServer.Description; var provider = new WebServerClient(description); provider.ClientIdentifier = CLIENT_ID; provider.ClientSecret = CLIENT_SECRET; var authenticator = new OAuth2Authenticator<WebServerClient>( provider, GetAuthorization) { NoCaching = true }; ps = new PlusService(authenticator); } // Perform an authenticated API request to retrieve the list of // people that the user has made visible to the app. if (context.Request.Path.Contains("/people")) { // Get the PeopleFeed for the currently authenticated user. PeopleFeed pf = ps.People.List("me", PeopleResource.CollectionEnum.Visible).Fetch(); // This JSON, representing the people feed, will later be // parsed by the JavaScript client. string jsonContent = Newtonsoft.Json.JsonConvert.SerializeObject(pf); context.Response.ContentType = "application/json"; context.Response.Write(jsonContent); return; } // Disconnect the user from the application by revoking the tokens // and removing all locally stored data associated with the user. if (context.Request.Path.Contains("/disconnect")) { // Perform a get request to the token endpoint to revoke the // refresh token. _authState = (IAuthorizationState)context.Session["authState"]; string token = (_authState.RefreshToken != null) ? _authState.RefreshToken : _authState.AccessToken; WebRequest request = WebRequest.Create( "https://accounts.google.com/o/oauth2/revoke?token=" + token); WebResponse response = request.GetResponse(); // Remove the cached credentials. context.Session["authState"] = null; // You could reset the state in the session but you must also // reset the state on the client. // context.Session["state"] = null; context.Response.Write( response.GetResponseStream().ToString().ToCharArray()); return; } }
/// <summary> /// Processes the request based on the path. /// </summary> /// <param name="context">Contains the request and response.</param> public void ProcessRequest(HttpContext context) { // Get the code from the request POST body. string accessToken = context.Request.Params["access_token"]; string idToken = context.Request.Params["id_token"]; // Validate the ID token if (idToken != null) { JWTSecurityToken token = new JWTSecurityToken(idToken); JWTSecurityTokenHandler jwt = new JWTSecurityTokenHandler(); // Configure validation Byte[][] certBytes = getCertBytes(); for (int i = 0; i < certBytes.Length; i++) { X509Certificate2 certificate = new X509Certificate2(certBytes[i]); X509SecurityToken certToken = new X509SecurityToken(certificate); // Set up token validation TokenValidationParameters tvp = new TokenValidationParameters(); tvp.AllowedAudience = CLIENT_ID; tvp.SigningToken = certToken; tvp.ValidIssuer = "accounts.google.com"; // Enable / disable tests tvp.ValidateNotBefore = false; tvp.ValidateExpiration = true; tvp.ValidateSignature = true; tvp.ValidateIssuer = true; // Account for clock skew. Look at current time when getting the message // "The token is expired" in try/catch block. // This is relative to GMT, for example, GMT-8 is: tvp.ClockSkewInSeconds = 3600 * 13; try { // Validate using the provider ClaimsPrincipal cp = jwt.ValidateToken(token, tvp); if (cp != null) { its.valid = true; its.message = "Valid ID Token."; } } catch (Exception e) { // Multiple certificates are tested. if (its.valid != true) { its.message = "Invalid ID Token."; } if (e.Message.IndexOf("The token is expired") > 0) { // TODO: Check current time in the exception for clock skew. } } } // Get the Google+ id for this user from the "sub" claim. Claim[] claims = token.Claims.ToArray<Claim>(); for (int i = 0; i < claims.Length; i++) { if (claims[i].Type.Equals("sub")) { its.gplus_id = claims[i].Value; } } } // Use Tokeninfo to validate the user and the client. var tokeninfo_request = new Oauth2Service().Tokeninfo(); tokeninfo_request.Access_token = accessToken; // Use Google as a trusted provider to validate the token. // Invalid values, including expired tokens, return 400 Tokeninfo tokeninfo = null; try { tokeninfo = tokeninfo_request.Fetch(); if (tokeninfo.Issued_to != CLIENT_ID){ ats.message = "Access Token not meant for this app."; }else{ ats.valid = true; ats.message = "Valid Access Token."; ats.gplus_id = tokeninfo.User_id; } } catch (Exception stve) { ats.message = "Invalid Access Token."; } // Use the wrapper to return JSON token_status_wrapper tsr = new token_status_wrapper(); tsr.id_token_status = its; tsr.access_token_status = ats; context.Response.StatusCode = 200; context.Response.ContentType = "text/json"; context.Response.Write(JsonConvert.SerializeObject(tsr)); }