YouTube API refresh token flow - asp.net-web-api

I am building an ASP.NET Web API. I am using YouTube API to upload videos on YouTube. I have managed to implement the OAuth with refresh token flow. After generating a refresh token I am using the following code for all the subsequent calls to YouTube API.
var token = new TokenResponse { RefreshToken = REFRESH_TOKEN };
var credentials = new UserCredential(new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets { ClientId = CLIENT_ID, ClientSecret = CLIENT_SECRET }
}), "user", token);
YouTubeService service = new YouTubeService((new BaseClientService.Initializer()
{
HttpClientInitializer = credentials
}));
I want to know when this refresh token will expire and how I would regenerate this refresh token without any user input/interaction so that end user does not see a Google account selection screen (in my case I see two accounts, a gmail one and a YouTube channel's one).
Also, if I have one refresh token generated, then I do not get a refresh token in response if I try to initiate the OAuth process again by using https://accounts.google.com/o/oauth2/auth again. Can I only have one refresh token at a time?

There are examples, also in the documentation and other similar questions here. However, maybe not in your programming language.
The refreshToken should be saved by you and reused. It is valid until it gets revoked by the user himself. Getting a refreshToken requiers user interaction.
Once you have obtained the refreshToken use it in another request to obtain an accessToken. The accessToken is needed in YouTube API requests for some data access. An accesToken expires after 1 hour (3600 seconds), although this period could be changed in the future.

Related

How to send JWT from spring boot to Flutter?

I would like to know what's the best and secured way to send the JWT generated in my spring boot app to my Flutter mobile app. I can send it in the response body which I know that's not a good practice in web but is it a problem when it comes to mobile?
This is how I return the JWT:
public ResponseEntity<?> signin(String username, String password) {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
String token= jwtTokenProvider.createToken(username, userRepository.findByUsername(username).getRoles());
//return new JwtResponse(token,username,userRepository.findByUsername(username).getRoles());
return ResponseEntity.ok(new AuthToken(token));
} catch (AuthenticationException e) {
throw new CustomException("Invalid username/password supplied", HttpStatus.UNPROCESSABLE_ENTITY);
}
}
IT doesn't matter how you send your JWT token, in the Header or in the body portion. it can easily be seen and copied by others through some app on the client-side.
The steps you can take to secure your data is
firstly You need to check if the Token is tampered with by the client, every time you receive a JWT token from the client
Secondly, you should add a created-at field where you send a timestamp of when the Token was created. This will prevent someone to pose as the owner of the token forever after this is stolen, as this Token will be invalid after some time.
and you can send your token simply in the body part of the response after taking those security measures. Of course, there are many more best practices to follow but these are a good starting point.

Implement refresh token in Spring Security + Angular

I'm working on this Spring Security implementation with OAuth2 and JWT:
According to the author I can access resources using token this way:
To access a resource use (you'll need a different application which has configured ResourceServer):
http localhost:8080/users 'Authorization: Bearer '$ACCESS_TOKEN
About this step:
To use the refresh token functionality:
http --form POST adminapp:password#localhost:9999/oauth/token grant_type=refresh_token refresh_token=$REFRESH_TOKEN
It's not clear for me when I need to refresh the token and how to handle this part into Angular.
When the Token expires do I need to first send request to the endpoint for refreshing the token and then to the login page?
How this case should be implemented?
At the time of authentication, two JWTs will be created - access token and refresh token. Refresh token will have longer validity. Both the tokens will be written in cookies so that they are sent in every subsequent request.
On every REST API call, the tokens will be retrieved from the HTTP header. If the access token is not expired, check the privileges of the user and allow access accordingly. If the access token is expired but the refresh token is valid, recreate new access token and refresh token with new expiry dates and sent back through Cookies
Access tokens carry the necessary information to access a resource directly. In other words, when a client passes an access token to a server managing a resource, that server can use the information contained in the token to decide whether the client is authorized or not. Access tokens usually have an expiration date and are short-lived.
Refresh tokens carry the information necessary to get a new access token. In other words, whenever an access token is required to access a specific resource, a client may use a refresh token to get a new access token issued by the authentication server. Common use cases include getting new access tokens after old ones have expired, or getting access to a new resource for the first time. Refresh tokens can also expire but are rather long-lived.
High level code
authenticate()
public ResponseEntity<OAuth2AccessToken> authenticate(HttpServletRequest request, HttpServletResponse response, Map<String, String> params) {
try {
String username = params.get("username");
String password = params.get("password");
boolean rememberMe = Boolean.valueOf(params.get("rememberMe"));
OAuth2AccessToken accessToken = authorizationClient.sendPasswordGrant(username, password);
OAuth2Cookies cookies = new OAuth2Cookies();
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
cookies.addCookiesTo(response);
if (log.isDebugEnabled()) {
log.debug("successfully authenticated user {}", params.get("username"));
}
return ResponseEntity.ok(accessToken);
} catch (HttpClientErrorException ex) {
log.error("failed to get OAuth2 tokens from UAA", ex);
throw new BadCredentialsException("Invalid credentials");
}
}
refreshToken()
Try to refresh the access token using the refresh token provided as a cookie. Note that browsers typically send multiple requests in parallel which means the access token will be expired on multiple threads. We don't want to send multiple requests to UAA though, so we need to cache results for a certain duration and synchronize threads to avoid sending multiple requests in parallel.
public HttpServletRequest refreshToken(HttpServletRequest request, HttpServletResponse response, Cookie refreshCookie) {
//check if non-remember-me session has expired
if (cookieHelper.isSessionExpired(refreshCookie)) {
log.info("session has expired due to inactivity");
logout(request, response); //logout to clear cookies in browser
return stripTokens(request); //don't include cookies downstream
}
OAuth2Cookies cookies = getCachedCookies(refreshCookie.getValue());
synchronized (cookies) {
//check if we have a result from another thread already
if (cookies.getAccessTokenCookie() == null) { //no, we are first!
//send a refresh_token grant to UAA, getting new tokens
String refreshCookieValue = OAuth2CookieHelper.getRefreshTokenValue(refreshCookie);
OAuth2AccessToken accessToken = authorizationClient.sendRefreshGrant(refreshCookieValue);
boolean rememberMe = OAuth2CookieHelper.isRememberMe(refreshCookie);
cookieHelper.createCookies(request, accessToken, rememberMe, cookies);
//add cookies to response to update browser
cookies.addCookiesTo(response);
} else {
log.debug("reusing cached refresh_token grant");
}
//replace cookies in original request with new ones
CookieCollection requestCookies = new CookieCollection(request.getCookies());
requestCookies.add(cookies.getAccessTokenCookie());
requestCookies.add(cookies.getRefreshTokenCookie());
return new CookiesHttpServletRequestWrapper(request, requestCookies.toArray());
}
}

How to login to Google API in a server app and use Google Plus?

The initial task was as simple as this: get the latest post from a Google+ page.
It took 3 days now to find out that all examples on the Internet seem to be either outdated or wrong or not valid. Google developer docs also don't provide much help, complicating things more and more with every new confusing documentation page. So, guys, I'm giving up.
First I tried to implement OAuth 2.0 procedure which was documented in their docs (https://developers.google.com/identity/protocols/OAuth2WebServer). As its title implies it is exactly about connecting from a server app. I followed it, and at first glance, it worked: I got the back call, successfully authenticated, fetched access token and stored it and made a simple call to fetch the posts.
// Initialization
$this->googleClient = new Google_Client();
$this->googleClient->setAuthConfig(Json::decode($config->get('client_json')));
$this->googleClient->setAccessType('offline');
$this->googleClient->setIncludeGrantedScopes(TRUE);
$this->googleClient->addScope('https://www.googleapis.com/auth/plus.me');
$this->googleClient->setRedirectUri(Url::fromRoute('mymodule.gplus.callback')->setAbsolute()->toString());
// The callback
$client->authenticate($code);
$accessToken = $client->getAccessToken();
(The only thing which seemed silly here - is the scope. I had no idea what scope should I claim for if I need to just read a public post from a public page, so I just picked the first random entry which looked related.)
As I said I got the token and could fetch my posts:
// Using Google_Service_Plus
$this->client()->setAccessToken($access_token);
$this->googleServicePlus = new Google_Service_Plus($this->client($reset));
$this->googleServicePlus->activities->listActivities($endpoint, 'public', ['maxResults' => 1]);
But after 1 hour it just stopped working claiming that the token is outdated or something and it needs to be refreshed. And here comes the showstopper: I found no way to refresh the token. $response from authenticate() doesn't return refresh token anymore (although it's been mentioned many times in other answers) so I don't even have a way to refresh it.
I tried digging in the library (from my composer.json: "google/apiclient": "^2.0") and figured out that authenticate() method is actually deprecated there are few other methods which seem to play with tokens. I tried \Google_Client::fetchAccessTokenWithAssertion() which asked for some Application Default Credentials... which leads us to completely different topic and way of authentication described here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
So should I abandon everything which I did and now implement something new? How could I just do this simple task of fetching news?
Sorry for the long question.
The process you are following is good. The problem you are having is refreshing the token. Although the official documentation states:
If you use a Google API Client Library, the client object refreshes the access token as needed as long as you configure that object for offline access.
It does not explain how to do it using the PHP Client Library. This was a problem for me too so this is the approach I'm taking and hopefully it can help you.
// 1. Build the client object
$client = new Google_Client();
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/index.php');
$client->setAuthConfig("client_secret.json");
$client->addScope($scopes);
$client->setAccessType("offline");
I normally save the Access Token to the session, therefore before proceeding, I check if the access token is already saved to the session. If it is, then I proceed to check if the access token is already expired. If it is, then I proceed to refresh the access token, and then I proceed to make the API Call.
// 2. Check if the access token is already saved to session
if( isset($_SESSION["access_token"]) && ($_SESSION["access_token"]) ) {
//set access token before checking if already expired
$client->setAccessToken($_SESSION["access_token"]);
//check if access token is already expired and refresh if so
if ($client->isAccessTokenExpired()) {
$refreshToken = $_COOKIE["refresh_token"]; //get refresh token
$client->refreshToken($refreshToken); // refresh the access token
}
//get new access token and save it to session
$_SESSION['access_token'] = $client->getAccessToken();
// set access token after checking if already expired
$client->setAccessToken($_SESSION["access_token"]);
$plusService = new Google_Service_Plus($client);
$optParams = array(
"maxResults" => 5,
"pageToken" => null
);
$activitiesList = $plusService->activities->listActivities("+cnn", "public", $optParams);
$activities = $activitiesList->getItems();
foreach ($activities as $activity ) {
print_r($activity);
print "<br>**********************<br>";
}
}
If the access token is not saved to the session, this means that the authentication and authorization has not taken place, so I proceed to authenticate the user.
// 3. Authenticate user since access token is not saved to session
else {
if( !isset($_GET["code"]) ){ //get authorization code
$authUrl = $client->createAuthUrl();
header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));
} else { //exchange authorization code for access token
$client->authenticate($_GET['code']); //authenticate client
//get access token and save it to session
$_SESSION['access_token'] = $client->getAccessToken();
//save refresh token to a Cookie
$refreshToken = $_SESSION["access_token"]["refresh_token"];
setcookie("refresh_token", $refreshToken, time() + (86400 * 30), "/");
$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/index.php';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
}
Please note: For demonstration purposes, I'm saving the refresh token to a cookie in this example; However, it is well known that you should not save this info to a cookie but instead to a secure database. Also, the authenticate() method is not deprecated, it's just an alias for the method fetchAccessTokenWithAuthCode(). Another thing, the scope you are using is not silly, since you are fetching info from a public page, according to the documentation here and here, I intuited that I should only allow access to Know who you are on Google https://www.googleapis.com/auth/plus.me.

Authenticating user for IdentityServer on the WebApi side

I am new at this. Can someone please help me, since I am going crazy over my problem for nearly a month now :(
In short: I have identity server project, an webapi project and angular client. Client request to authenticate and gets id_token and access_token (all good), access_token send to webapi project where I have:
var idServerBearerTokenAuthOptions = new IdentityServerBearerTokenAuthenticationOptions {
Authority = "https://localhost:11066/IdentityServer/identity",
ValidationMode = ValidationMode.ValidationEndpoint,
AuthenticationType = "Bearer",
RequiredScopes = new[] { "permissions", "openid" },
DelayLoadMetadata = true
};
app.UseIdentityServerBearerTokenAuthentication(idServerBearerTokenAuthOptions);
and I have Autofac which should get me the current logedin user
builder.RegisterApiControllers(Assembly.GetExecutingAssembly()).InstancePerRequest();
builder.Register(c => new ClaimsIdentityApiUser((ClaimsIdentity)Thread.CurrentPrincipal.Identity)).As<IApiUser>().InstancePerRequest();
BUT Thread.CurrentPrincipal.Identity has nothing, and
also ClaimsPrincipal.Current.Identity has nothing. What am I missing??
p.s. Similar problem to this question Protecting webapi with IdentityServer and Autofac - can't get claims but obviously not same solution nor set up.
a) user should always be retrieved from ApiController.User (or the RequestContext)
b) token validation might fail for some reason use this resource to enable logging for the token validation middleware:
https://identityserver.github.io/Documentation/docsv2/consuming/diagnostics.html
c) are you using JWTs or reference tokens? For JWTs you can set the ValidationMode to Local

Should I store the ID token persistently in Google sign in?

I am using Google sign-in in my app, and I will send the ID token to my backhand server as soon as the user signed in and the ID token is retrieved. For now I will add the ID token to the header of each HTTP request, and I validate it, get user's ID and respond data back to my app. I am wondering if it is OK to store the ID token persistently and use it for all the future request. Will the ID token change or expire some time? If so, how to get new ID token? I can't find any approach other than asking user to sign in again. Or should I only validate the ID token for once and use ID directly in the future requests?
Don't store an ID token. Google ID tokens are issued for one hour validity and will expire, you can simply use silentSignIn in your app to get a new one without any user interaction. If your existing token hasn't expired yet, you will get the (cached) version back (OptionalPendingResult returned will have isDone() == true); if it expired already, you will get a refreshed one (but it will take a little longer and thus OptionalPendingResult isDone() will be false).
Here is sample code (UI thread, see note below about a worker thread):
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
...
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result); // result.getSignInAccount().getIdToken(), etc.
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(GoogleSignInResult googleSignInResult) {
handleSignInResult(googleSignInResult); // result.getSignInAccount().getIdToken(), etc.
}
});
}
Keep in mind whether you call silentSignIn on a UI thread or worker thread. If you call it on worker thread, take a look at this post with blockingConnect() + await() which simplifies the code a lot:
Silent sign in to retrieve token with GoogleApiClient

Resources