JWT Token valid by JwtSecurityTokenHandler but no valid by JWT IO - token

My token is generate from X509Certificate2
My problem:
Validation works via JwtSecurityTokenHandler but not when I go to jwt.io
public static bool ValidateToken(string token, X509Certificate2 certificate)
{
var tokenHandler = new JwtSecurityTokenHandler();
var securityKey = new X509SecurityKey(certificate);
/*var rsaSecurityKey = new RsaSecurityKey(certificate.GetRSAPrivateKey())
{
KeyId = "S5669243590045587695"
};*/
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = securityKey,
ValidateAudience = true,
ValidateIssuer = true,
ValidAudience = ConfigurationManager.AppSettings["authorizationServer"] + "/realms/" + ConfigurationManager.AppSettings["realm"],
ValidIssuer = ConfigurationManager.AppSettings["clientID"],
};
SecurityToken validatedToken;
try
{
tokenHandler.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception _ex)
{
return false;
}
But not when I use jwt.io
I fill in my public key
Base64UrlEncode(Encoding.UTF8.GetBytes(_cert.GetPublicKeyString()))
Do you have an idea ?
Thx

Related

How to make API request from Google authentication in ASP.NET?

I do not understand how I can get the authorization code/access token to make a request.
This is my .AddGoogle():
builder.Services.AddAuthentication(o =>
{
o.DefaultChallengeScheme = "Google";
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie().AddGoogle("Google",options =>
{
IConfigurationSection auth = builder.Configuration.GetSection("Authentication:Google");
options.ClientId = auth["ClientId"];
options.ClientSecret = auth["ClientSecret"];
options.CallbackPath = "/Home";
options.AuthorizationEndpoint += "?prompt=consent";
options.SaveTokens = true;
options.Scope.Add(FitnessService.Scope.FitnessActivityRead);
});
So I log in / register, and then what?
I've tried this method
UserCredential credential;
var flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer()
{
ClientSecrets = new ClientSecrets()
{
ClientId = configuration.GetSection("Authentication:Google")["ClientId"],
ClientSecret = configuration.GetSection("Authentication:Google")["ClientSecret"]
},
Scopes = new[] {
FitnessService.Scope.FitnessActivityRead,
FitnessService.Scope.FitnessActivityWrite,
FitnessService.Scope.FitnessSleepRead,
FitnessService.Scope.FitnessSleepWrite
},
});
var token = new TokenResponse
{
AccessToken = "",
RefreshToken = ""
};
credential = new UserCredential(flow, "user", token);
FitnessService fitnessService = new FitnessService(new BaseClientService.Initializer()
{
ApplicationName = "Exrecise App",
HttpClientInitializer = credential
});
var resp = await fitnessService.Users.Sessions.List("me").ExecuteAsync();
But how do I get the tokens to begin with?
I even tried HttpClient (by pasting the token from OAuthplayground)
HttpClient http = new HttpClient();
var resp = await http.SendAsync(new HttpRequestMessage()
{
RequestUri = new UriBuilder("https://www.googleapis.com/fitness/v1/users/me/sessions").Uri,
Headers =
{
{ "Authorization","Bearer " }
},
Method = HttpMethod.Get
});
return Ok(await resp.Content.ReadAsStringAsync());
Same problem, how do I get the access token from the currently signed in user?
I looked everywhere, but every solution is either outdated or is unclear or just doesn't work for some reason.

How to decrypt JWE token in IdentityServer4? Implementing JWE in Identity Server4?

I am trying to implement JWE in IdentityServer4. But getting exception
SecurityTokenKeyWrapException: IDX10659: UnwrapKey failed, exception from cryptographic operation: '[PII of type 'Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException' is hidden.
Basically I am following https://www.scottbrady91.com/identity-server/encrypting-identity-tokens-in-identityserver4 for the encryption and decryption.
Encrypting "id_token" by overriding CreateTokenAsync method of DefaultTokenCreationService
public class JweTokenCreationService : DefaultTokenCreationService
{
public JweTokenCreationService(ISystemClock clock, IKeyMaterialService keys,
IdentityServerOptions options, ILogger<DefaultTokenCreationService> logger) :
base(clock, keys, options, logger)
{
}
public override async Task<string> CreateTokenAsync(Token token)
{
var filePath = Path.Combine(Environment.CurrentDirectory, "server.crt");
var certificate = new X509Certificate2(filePath); // certificate for encryption
if (token.Type == IdentityServerConstants.TokenTypes.IdentityToken)
{
var payload = await base.CreatePayloadAsync(token);
var handler = new JsonWebTokenHandler();
var jwe = handler.CreateToken(
payload.SerializeToJson(),
await Keys.GetSigningCredentialsAsync(),
new X509EncryptingCredentials(new X509Certificate2(certificate))
);
return jwe; // encrypted token
}
return await base.CreateTokenAsync(token);
}
}
The "id_token" gets encrypted sucessfully.
Decrypting "id_token" in MVC client.
In the Startup.cs class of MVC client I have added the code.
var filePath = Path.Combine(Environment.CurrentDirectory, "server.pfx");
var certificate = new X509Certificate2(filePath, "123456", X509KeyStorageFlags.Exportable);
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Add("api1");
options.SaveTokens = true;
options.TokenValidationParameters = new TokenValidationParameters
{
TokenDecryptionKey = new X509SecurityKey(certificate) // set the TokenDecryptionKey on OpenIdConnectsOptions’s TokenValidationParameters to the appropriate private key
};
});
Result:
While running the application the "id_token" gets encrypted successfully (Checked with debugger). But in client app its throwing exception SecurityTokenKeyWrapException: IDX10659: UnwrapKey failed, exception from cryptographic operation.
Tried Workarounds:
a. Tried using RSA key instead of X5t as mentioned here IDX10659 error when using X509Certficate to decrypt JWT token
options.TokenValidationParameters = new TokenValidationParameters
{
TokenDecryptionKey = new
RsaSecurityKey(certificate.GetRSAPrivateKey().ExportParameters(true))
};
Still getting same exception.
b. Tried Using a Custom Crypto Provider like https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/615
Created a Custom CryptoProviderFactory
public class UnwrappingCryptoProviderFactory : CryptoProviderFactory
{
public UnwrappingCryptoProviderFactory()
{
System.Console.WriteLine("UnwrappingCryptoProviderFactory invoked");
}
public override KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string algorithm)
{
switch (key)
{
case X509SecurityKey x509Key:
return new RsaKeyWrapProvider(x509Key, algorithm, true);
default:
return base.CreateKeyWrapProvider(key, algorithm);
}
}
}
In MVC client used this custom CryptoProviderFactory
var filePath = Path.Combine(Environment.CurrentDirectory, "server.pfx");
var certificate = new X509Certificate2(filePath, "123456", X509KeyStorageFlags.Exportable);
var key = new X509SecurityKey(certificate);
{
// Custom provider to ensure we can unwrap the key properly when decrypting the token
CryptoProviderFactory = new UnwrappingCryptoProviderFactory()
};
Here is the TokenValidationParameters
options.TokenValidationParameters = new TokenValidationParameters
{
TokenDecryptionKey = key
};
Still getting same exception.
Any help/suggestion would be really helpful. Thanks!

ADFS When and how to use the refresh token [UseOpenIdConnectAuthentication - ASP.NET MVC5]

In the Startup.Auth.cs, on the AuthorizationCodeReceived notification, I receive both the accessToken (lifetime 1h) and the refreshToken (24h). The problem is I don't know when to request a new access token and how to request it. A full-code example would be very useful, as I am a noob, regarding adfs and owin.
The users are, at some point, redirected to the adfs and then back to the website, but they lose their session and errors occur.
here's the full code dump:
public partial class Startup
{
private readonly string authority = ConfigurationManager.AppSettings["auth:Authority"];
private readonly string clientId = ConfigurationManager.AppSettings["auth:ClientId"];
private readonly string clientSecret = ConfigurationManager.AppSettings["auth:ClientSecret"];
private readonly string metadataAddress = ConfigurationManager.AppSettings["auth:MetadataAddress"];
private readonly string postLogoutRedirectUri = ConfigurationManager.AppSettings["auth:PostLogoutRedirectUri"];
private readonly string redirectUri = ConfigurationManager.AppSettings["auth:RedirectUri"];
private readonly string tokenEndpoint = ConfigurationManager.AppSettings["auth:TokenEndpoint"];
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies",
CookieHttpOnly = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
Authority = this.authority,
ClientId = this.clientId,
ClientSecret = this.clientSecret,
MetadataAddress = this.metadataAddress,
RedirectUri = this.redirectUri,
ResponseType = "code id_token",
Scope = "offline_access openid profile customscope",
RequireHttpsMetadata = false, //bool.Parse(ConfigurationManager.AppSettings["IsProduction"]),
PostLogoutRedirectUri = this.postLogoutRedirectUri,
SignInAsAuthenticationType = "Cookies",
AuthenticationMode = AuthenticationMode.Active,
UseTokenLifetime = false,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
this.tokenEndpoint,
this.clientId,
this.clientSecret);
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var handler = new JwtSecurityTokenHandler();
var identityToken = handler.ReadJwtToken(tokenResponse.IdentityToken);
var upn = identityToken.Claims.ToList().FirstOrDefault(c => c.Type == "upn")?.Value;
var localUser = new UserReader().GetByUpn(upn);
// mandatory for logout user
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
if (localUser?.Id != null)
{
id.AddClaim(new Claim(ClaimTypes.Upn, localUser.Upn));
id.AddClaim(new Claim("Id", localUser.Id.ToString()));
id.AddClaim(new Claim("Name", localUser.Name));
id.AddClaim(new Claim("RoleId", localUser.Role.Id.ToString()));
id.AddClaim(new Claim("RoleName", localUser.Role.Name));
id.AddClaim(new Claim("IsOffice", localUser.Role.IsOffice.ToString()));
id.AddClaim(new Claim("IsInvestment", localUser.Role.IsInvestment.ToString()));
}
id.AddClaim(new Claim("identityToken", tokenResponse.IdentityToken));
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at",
DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn).ToString(CultureInfo.InvariantCulture)));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name",
"role"),
n.AuthenticationTicket.Properties);
},
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
{
// valoarea idTokenHint nu persistă și fără, nu face redirect către login, imediat după log-out
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
n.ProtocolMessage.PostLogoutRedirectUri = this.postLogoutRedirectUri;
return Task.FromResult(0);
},
AuthenticationFailed = (context) =>
{
if (context.Exception.Message.Contains("IDX21323") || context.Exception.Message.Contains("IDX10311"))
{
// Cateodata da eroare ca nu gaseste nonce cookie, si atunci trebuie relogat la ADFS
context.HandleResponse();
context.OwinContext.Authentication.Challenge();
}
return Task.FromResult(0);
}
}
});
}
}
To use the refresh token, make a POST request to the ADFS token endpoint with:
grant_type=refresh_token
Include the refresh token as well as the client credentials.
e.g.
https://ADFS tenant/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
The thing is that the MSAL library should handle this for you.
You don't need to write the code.
Have a look at the code samples here under ADAL / MSAL.

Connecting with Javascript to Web API protected by IdentityServer3

I have a Asp.NET MVC / WebAPI project with an embedded IdentityServer3.
I want both MVC and WebAPI to be protected by the IdentityServer. So I have used Authorize attribute on both MVC controllers and API controllers.
When surfing to my test page (which is protected) I get redirected to the IdentityServer login page. I enter my username and password and get authenticated and redirected back.
On the page I have a button that triggers a GET from javascript, with my access token in the authorization header, to my protected API. But here it fails with a 401 Unauthorized.
I get the access token to javascript by rendering it to the page with Razor.
I have 1 client in the IdentityServer set to hybrid flow. MVC uses cookies, while the API uses bearer tokens.
On my API HttpConfiguration I have set SuppressDefaultHostAuthentication. If I remove that line everything works, but then it uses cookies for the API which I don't want.
I use only HTTP and RequireSsl=false for now to avoid potential certificate problems.
I have tried for days to get this to work but I'm getting nowhere.
I don't even know how to debug this to get to cause of the 401.
By now I recognize just about every page that google suggests when I search for help.
Any ideas what it could be or how to debug this?
Here is my Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Trace()
.CreateLogger();
// MVC client
string authBaseAddress = "http://localhost:50319/identity";
string tokenEndpoint = authBaseAddress + "/connect/token";
string userInfoEndpoint = authBaseAddress + "/connect/userinfo";
string redirectUri = "http://localhost:50319/";
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "hybrid_clients",
Authority = authBaseAddress,
RedirectUri = redirectUri,
ResponseType = "code id_token token",
Scope = "openid profile roles sampleApi offline_access",
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
},
SignInAsAuthenticationType = "Cookies",
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthorizationCodeReceived = async n =>
{
// use the code to get the access and refresh token
var tokenClient = new TokenClient(
tokenEndpoint,
"hybrid_clients",
"secret");
var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, n.RedirectUri);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
// use the access token to retrieve claims from userinfo
var userInfoClient = new UserInfoClient(
new Uri(userInfoEndpoint),
tokenResponse.AccessToken);
var userInfoResponse = await userInfoClient.GetAsync();
// create new identity
var id = new ClaimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
id.AddClaim(new Claim("access_token", tokenResponse.AccessToken));
id.AddClaim(new Claim("expires_at", DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToLocalTime().ToString()));
id.AddClaim(new Claim("refresh_token", tokenResponse.RefreshToken));
id.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken));
id.AddClaim(new Claim("sid", n.AuthenticationTicket.Identity.FindFirst("sid").Value));
n.AuthenticationTicket = new AuthenticationTicket(
new ClaimsIdentity(id.Claims, n.AuthenticationTicket.Identity.AuthenticationType, "name", "role"),
n.AuthenticationTicket.Properties);
},
// Attach the id_token for the logout roundtrip to IdentityServer
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest)
{
var idTokenHint = n.OwinContext.Authentication.User.FindFirst("id_token");
if (idTokenHint != null)
{
n.ProtocolMessage.IdTokenHint = idTokenHint.Value;
}
}
return Task.FromResult(0);
}
}
});
AntiForgeryConfig.UniqueClaimTypeIdentifier = Constants.ClaimTypes.Subject;
JwtSecurityTokenHandler.InboundClaimTypeMap = new Dictionary<string, string>();
// web api
app.Map("/api", a =>
{
var config = new HttpConfiguration();
a.UseCors(CorsOptions.AllowAll);
a.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
//AuthenticationMode = AuthenticationMode.Active,
Authority = authBaseAddress,
RequiredScopes = new[] { "sampleApi" },
DelayLoadMetadata = true
});
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
config.MapHttpAttributeRoutes();
a.UseWebApi(config);
});
// Identity server
app.Map("/identity", idsrvApp =>
{
idsrvApp.UseCors(CorsOptions.AllowAll);
idsrvApp.UseIdentityServer(new IdentityServerOptions
{
SiteName = "Embedded IdentityServer",
SigningCertificate = LoadCertificate(),
Factory = new IdentityServerServiceFactory()
.UseInMemoryUsers(Users.Get())
.UseInMemoryClients(Clients.Get())
.UseInMemoryScopes(Scopes.Get()),
AuthenticationOptions = new IdentityServer3.Core.Configuration.AuthenticationOptions()
{
EnablePostSignOutAutoRedirect = true // Automatically redirects back to the client on signout
},
RequireSsl = false,
});
});
}
X509Certificate2 LoadCertificate()
{
return new X509Certificate2(
string.Format(#"{0}\bin\idsrv3test.pfx", AppDomain.CurrentDomain.BaseDirectory), "idsrv3test");
}
}
My client
new Client
{
Enabled = true,
ClientName = "Hybrid Clients",
ClientId = "hybrid_clients",
Flow = Flows.Hybrid,
//AllowAccessTokensViaBrowser = false,
RedirectUris = new List<string>
{
"http://localhost:50319/"
},
PostLogoutRedirectUris = new List<string>
{
"http://localhost:50319/"
},
AllowedScopes = new List<string>
{
"openid",
"profile",
"email",
"roles",
"address",
"all_claims",
"sampleApi",
"offline_access"
},
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AccessTokenType = AccessTokenType.Reference,
LogoutSessionRequired = true
},
My scopes
public static class Scopes
{
public static IEnumerable<Scope> Get()
{
var scopes = new List<Scope>
{
StandardScopes.OpenId,
StandardScopes.Profile,
StandardScopes.Email,
StandardScopes.Address,
StandardScopes.OfflineAccess,
StandardScopes.RolesAlwaysInclude,
StandardScopes.AllClaims,
new Scope
{
Enabled = true,
Name = "roles",
Type = ScopeType.Identity,
Claims = new List<ScopeClaim>
{
new ScopeClaim("role")
}
},
new Scope
{
Enabled = true,
DisplayName = "Sample API",
Name = "sampleApi",
Description = "Access to a sample API",
Type = ScopeType.Resource
}
};
return scopes;
}
}
My API
[Authorize]
public class SecuredApiController : ApiController
{
public IHttpActionResult Get()
{
var user = User as ClaimsPrincipal;
var claims = from c in user.Claims
select new
{
type = c.Type,
value = c.Value
};
return Json(claims);
}
}
Part of my Razor view
<button data-bind="click:callApi">Call API</button>
<span data-bind="text:apiResult"></span>
<script>
$(function() {
ko.myViewModel = new ClientAppViewModel('#ViewData["access_token"]');
ko.applyBindings(ko.myViewModel);
});
</script>
My JavaScript (KnockoutJS) that calls SecuredApi
function ClientAppViewModel(accessToken) {
var self = this;
self.accessToken = accessToken;
self.apiResult = ko.observable('empty');
self.callApi = function () {
console.log('CallApi');
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:50319/api/SecuredApi");
xhr.onload = function () {
self.apiResult(JSON.stringify(JSON.parse(xhr.response), null, 2));
};
xhr.setRequestHeader("Authorization", "Bearer " + self.accessToken);
xhr.send();
}
}
The Katana logs for the API are what you want. With these you will see why the API is returning a 401.
You can access these using the Microsoft.Owin log source (see Katana Documentation)

How can I use ASP.NET MVC Owin AccessToken in Google.Apis call?

I'm trying to use the AccessToken provided by Owin in Google.Apis requests but I'm receiveing the exception System.InvalidOperationException (Additional information: The access token has expired but we can't refresh it).
My configuration of Google Authentication is OK and I can successfully login into my application with it. I store the context.AccessToken as a Claim in the authentication callback (OnAuthenticated "event" of GoogleOAuth2AuthenticationProvider).
My Startup.Auth.cs configuration (app.UseGoogleAuthentication(ConfigureGooglePlus()))
private GoogleOAuth2AuthenticationOptions ConfigureGooglePlus()
{
var goolePlusOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = "Xxxxxxx.apps.googleusercontent.com",
ClientSecret = "YYYYYYzzzzzz",
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("Google_AccessToken", context.AccessToken));
return Task.FromResult(0);
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.email");
return goolePlusOptions;
}
The code in which the exception is throwed (Execute() method)
var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
var accessTokenClaim = externalIdentity.FindAll(loginProvider + "_AccessToken").First();
var secrets = new ClientSecrets()
{
ClientId = "Xxxxxxx.apps.googleusercontent.com",
ClientSecret = "YYYYYYzzzzzz"
};
IAuthorizationCodeFlow flow =
new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = secrets,
Scopes = new[] { PlusService.Scope.PlusLogin, PlusService.Scope.UserinfoEmail }
});
UserCredential credential = new UserCredential(flow, "me", new TokenResponse() { AccessToken = accessTokenClaim.Value });
var ps = new PlusService(
new BaseClientService.Initializer()
{
ApplicationName = "My App Name",
HttpClientInitializer = credential
});
var k = ps.People.List("me", PeopleResource.ListRequest.CollectionEnum.Visible).Execute();
Is there another way to get the original AccessToken or refresh it without pass thru the entire authentication process (the user is already authenticated)?
I need to query some GooglePlus profile data such as GivenName, familyName, gender, profile picture and profile url.
Linda helped me with an URL pointing to a new asp.net mvc sample (https://codereview.appspot.com/194980043/).
I just had to add AccessType = "offline" to GoogleOAuth2AuthenticationOptions and save some extra info to create a new instance of TokenResponse when I need.
Google Authentication Options
private GoogleOAuth2AuthenticationOptions ConfigureGooglePlus()
{
var goolePlusOptions = new GoogleOAuth2AuthenticationOptions()
{
AccessType = "offline",
ClientId = "Xxxxxxx.apps.googleusercontent.com",
ClientSecret = "Yyyyyyyyyy",
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = context =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("Google_AccessToken", context.AccessToken));
if (context.RefreshToken != null)
{
context.Identity.AddClaim(new Claim("GoogleRefreshToken", context.RefreshToken));
}
context.Identity.AddClaim(new Claim("GoogleUserId", context.Id));
context.Identity.AddClaim(new Claim("GoogleTokenIssuedAt", DateTime.Now.ToBinary().ToString()));
var expiresInSec = (long)(context.ExpiresIn.Value.TotalSeconds);
context.Identity.AddClaim(new Claim("GoogleTokenExpiresIn", expiresInSec.ToString()));
return Task.FromResult(0);
}
},
SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie
};
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.login");
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/plus.me");
goolePlusOptions.Scope.Add("https://www.googleapis.com/auth/userinfo.email");
return goolePlusOptions;
}
How to retrieve the credentials (using token info "stored" as claim)
private async Task<UserCredential> GetCredentialForApiAsync()
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "Xxxxxxxxx.apps.googleusercontent.com",
ClientSecret = "YYyyyyyyyyy",
},
Scopes = new[] {
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email" }
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var identity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ApplicationCookie);
var userId = identity.FindFirstValue("GoogleUserId");
var token = new TokenResponse()
{
AccessToken = identity.FindFirstValue("Google_AccessToken"),
RefreshToken = identity.FindFirstValue("GoogleRefreshToken"),
Issued = DateTime.FromBinary(long.Parse(identity.FindFirstValue("GoogleTokenIssuedAt"))),
ExpiresInSeconds = long.Parse(identity.FindFirstValue("GoogleTokenExpiresIn")),
};
return new UserCredential(flow, userId, token);
}

Resources