Personalized login button generated by Google - authentication

Google has a code generator that generates a personalized login with Google button. A sample of the code generated by the generator is as the following.
<div id="g_id_onload"
data-client_id="abcdefghijklmnopqrstuvwxyz"
data-context="signin"
data-ux_mode="popup"
data-callback="localhost:1234/callback"
data-nonce=""
data-auto_prompt="false">
</div>
<div class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left">
</div>
I have a few questions to understand this button better.
Why is there no client secret?
This button gets an ID token, but what about a refresh and access token? How could you refresh an expired ID token?
Is the generated code production-ready, or is it merely serve demo purposes?

Why is there no client secret?
Because this is client side JavaScript and there for uses implicit flow. The Implicit flow is a simplified OAuth flow used by JavaScript apps where the access token was returned immediately without an extra authorization code exchange step.
This button gets an ID token, but what about a refresh and access token? How could you refresh an expired ID token?
This is again client side JavaScript Implicit flow. Implicit flow does not return a refresh token. TO get a new id token after it has expired the user will need to login again.
Is the generated code production-ready, or is it merely serve demo purposes?
Google web identity is in production and what google is currently recommending we used for Client side JavaScript web applications.

Related

Clarification on Google Authentication and Authorization, with OAuth in 2022

I am writing an App and am trying to leverage Google for A&A. The app itself relies on access to the users Google Calendar, and so initially I leveraged their updated OAUTH2 library for A&A.
Here is my flow:
User goes to the index.html which has "https://accounts.google.com/gsi/client" script and google.accounts.oauth2.initCodeClient is called with my client_id, scopes, redirect url
<script src="https://accounts.google.com/gsi/client"></script>
<script>
let client;
function initClient() {
client = google.accounts.oauth2.initCodeClient({
client_id: 'xxxxx-xxxx.apps.googleusercontent.com',
scope:
'https://www.googleapis.com/auth/userinfo.profile \
https://www.googleapis.com/auth/userinfo.email \
https://www.googleapis.com/auth/calendar.readonly \
https://www.googleapis.com/auth/calendar.events',
ux_mode: 'redirect',
redirect_uri: 'http://localhost:5000/oauth2callback',
});
}
// Request an access token
function getAuthCode() {
client.requestCode();
}
The user clicks the login button, which kicks off requestCode() and they begin the login flow. They login or select their google account, then besides the unapproved app screen, they get to the consent screen with my requested scopes.
After, they are redirected to my expressjs endpoint and using the "googleapis" library I exchange with id_token for the access and refresh tokens.
...
const { tokens } = await oauth2Client.getToken(req.query.code); //exchange code for tokens
const userInfo = (
await oauth2Client.verifyIdToken({
idToken: tokens.id_token,
audience: config.google.clientID,
})
).payload;
if (!indexBy.email[userInfo.email]) { // check if user exists
const newUser = {
name: userInfo.name,
email: userInfo.email,
o_id: userInfo.sub,
picture: userInfo.picture,
r_token: tokens.refresh_token,
};
...
Ok, all good.... but not quite. The problem is, that next time the user wants to login to the app, they go through the entire flow again, including the consent screen (again).
So, after going through more docs, even looking at examples from google. I was surprised and I noticed that many of those apps used the passport oauth2 plugin :( Something i've done in the past, but was hoping to avoid that with the recently updated Google client and nodejs libraries.
Ok, how to not prompt for consent screen on subsequent logins?
Maybe separate A&A, so first I use "Sign In With Google" for Authentication, then when I get the user info, check if the user is already registered (hence I have already saved the refresh token) and they start the app.
On the other hand, if they are new (not in existing app user collection), after authenticating, I will then call the OAUTH2 authorization redirect, so again they on Googles site, this time to do the scopes api confirmation.
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed). Hopefully this will still work ok when things come up with expired/invalid refresh token (fingers crossed the default google library handles that).
When doing the Authorize for consent, can I pass something from the previous Authenticate flow so they don't need to do that again.
Or maybe when doing the Authenticate process (Google Identity Service), there is some flag or param so that if they have already consented, they don't have to do that again on subsequent logins.
Incase I wasn't clear, in a nutshell the question is: should I be doing Authenticate for login, separately from Authorization (oauth2 token). Or should I go right into the Authorization flow, which first Authenticates the user, and can I skip the Authorization consent screens if they've already done that. Or maybe there's another way which is the best practice.
Thanks for your attention.
Background info
Authentication is the act where by a user logs in into a system using their login and password. With authentication we know that the user is behind the machine. For this we use Open id connect, which was built on top of Oauth2. Open id connect returns and id_token which can be used to identify the user, it is often a jwt containing some claims to identify the subject or the user behind the Authentication.
The scope used for open id connect is profile and email. open id connect grants you consent to access a users profile information.
This is an example of the decrypted id token returned by google from a simple call using profile scope only. All this id token is telling you is who the user behind the machine is.
{
"iss": "https://accounts.google.com",
"azp": "4074087181.apps.googleusercontent.com",
"aud": "4074087181.apps.googleusercontent.com",
"sub": "1172004755672775346",
"at_hash": "pYlH4icaIx8PssR32_4qWQ",
"name": "Linda Lawton",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s96-c",
"given_name": "Linda",
"family_name": "Lawton",
"locale": "en",
"iat": 1655219027,
"exp": 1655222627
}
In the same call google also returned an access token. Now my call contained only the scope for profile, due to the fact that its open id connect. This means that I will only have access to the data that the profile scope would grant access to. In this case most of what is behind the Google people api.
Note: The user does not see a consent screen with open id connect, even though they are consenting to profile scope. It is assumed by signing into your account that the system you are logging into would have access to your profile info.
Authorization
Authorization is the process by which a user grants your application authorization to access their private user data. The user is shown a consent screen where they consent to your application accessing data defined by some scopes.
In the case of google calendar api there are serval
https://www.googleapis.com/auth/calendar See, edit, share, and permanently delete all the calendars you can access using Google Calendar
https://www.googleapis.com/auth/calendar.events View and edit events on all your calendars
https://www.googleapis.com/auth/calendar.events.readonly View events on all your calendars
https://www.googleapis.com/auth/calendar.readonly See and download any calendar you can access using your Google Calendar
https://www.googleapis.com/auth/calendar.settings.readonly View your Calendar settings
In this case you are only given an access token this is again Oauth2 it is authorization to access the users calendar data it is not authentication this is not related to login.
Your question
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed).
You would do both at the same time.
When you authencation your user make sure to include your google calendar scope then the access token and refresh token returned will grant you access to google calendar.
I am going to assume that you have some kind of user system. When you store the user be sure to store the refresh token that is returned.
As far as Authentication goes i will assume you either have a remember me system which will set a cookie on their machine and remember the user so that you can then get the refresh token from their system the next time they come back.
If they did not chose to select a remember me option then will then have to login every time they visit your site but part of the login will return the "sub": "1172004755672775346", this is the users id on google system so you can use that in your database to match the user when they come back.
Your question is quite complex and will depend upon the type of system you have what it is designed to do as well as what programming language you are using. That being said I hope this very long answer clears things up a bit.

best practices for refreshing access tokens automatically

I'm building a react native app which uses the spotify web api. I'm using the authorization code flow to authorize a user. First I get a authorization code which can be used to obtain an access token and a refresh token. Everything works!
The problem is: an access token is only valid for a limited amount of time. That's where the refresh token comes in. I understand this concept, but I'm breaking my head about how to implement this.
Let's say a users opens the app, requests an access token and uses this for some time. Then, the user closes the app. After 15 minutes, the users opens the app again. The access token has now expired, so I need to request a new access token.
I've come op with several "solutions". Can someone point me to the correct solution?
Solution 1:
Every time the user opens the app, I request a new access token and use this. Problem: when the user uses the app longer than the valid time of the access token, I won't work anymore.
Solution 2:
I use the access token that's stored in the secure storage on every request. When a request comes back with 'access token invalid' (I don't know the exact error code but you guys know what I mean), I request a new access token with the stored refresh token, and then I send the previous command again (with the new access token). But my question here is: can I use some kind of "wrapper function" which checks the response of the request, and if the response is "access token invalid", it automatically requests a new access token and runs the previous request again.
I think certainly correct solution is solution 2,and i think its clear enough.
and for using solution 2 you need somthing like wrapper function,yes its intelligently.
so you should use interceptor:
what is interceptor ?
You can intercept requests or responses before they are handled by then or catch.
in link below there is a good example of implementing refresh token in axios interceptor:
https://gist.github.com/Godofbrowser/bf118322301af3fc334437c683887c5f
I agree that Solution 2 is the best, each time you do a request you can check to see if the Access Token has expired, and if it has then you can request a new Access Token using the Refresh Token as you mentioned and then make your request, in my own project I do this in a FormatRequestHeadersAsync method which calls a CheckAndRenewTokenAsync method where I perform the following check, here shown in C#:
if(AccessToken?.Refresh != null && (AccessToken.Expiration < DateTime.UtcNow))
{
AccessToken = await GetRefreshTokenAsync(
AccessToken.Refresh,
AccessToken.TokenType,
cancellationToken);
}
You can store the Access Token and the Refresh Token and then use something similar to this before you make each request to the API this will refresh your token and then you can store the new Access Token and the existing Refresh Token.

OAuth Implicit flow Access Token expires every hour

I'm having a problem with the OAuth Implicit flow for the Google Assistant.
I managed to set up a OAuth server and got it to work. Here's the flow:
The user Is redirected to my endpoint, authenticates with a Google account, and gets send back to the Assistant with an Acces Token and result code=SUCCES.
In my fullfilment I get the users email address by doing a https request to: https://www.googleapis.com/plus/v1/people/me?access_token=access_token.
I then find the matching user in my database and add the acces token to the database for this user.
The next time the user logs in I check the acces token and greet the user by their name.
Now the problem is that this is the Implict flow which according to the documentation should have an access token that never expires:
Note: Google requires that access tokens issued using the implicit
flow never expire, so you don't need to record the grant time of an
access token, as you would with other OAuth 2.0 flows.
But the Assistant forces me to re-authenticate every hour, meaning the access token did expire.
My question is: Is this flow correct or am I missing something? Is there something I've done wrong in my OAuth endpoint?
I based my endpoint on https://developers.google.com/identity/protocols/OAuth2UserAgent.
<html>
<head>
<script src="https://apis.google.com/js/platform.js" async defer></script>
<meta name="google-signin-client_id" content="CLIENT_ID">
</head>
<body>
<script>
var YOUR_CLIENT_ID = 'CLIENT_ID';
function oauth2SignIn() {
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
//Get the state and redirect_uri parameters from the request
var searchParams = new URLSearchParams(window.location.search);
var state = searchParams.get("state");
var redirect_uri = searchParams.get("redirect_uri");
//var client_id = searchParams.get("client_id");
// Parameters to pass to OAuth 2.0 endpoint.
var params = {
'client_id': YOUR_CLIENT_ID,
'redirect_uri': redirect_uri,
'scope': 'email',
'state': state,
'response_type': 'token',
'include_granted_scopes': 'true'
};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
oauth2SignIn();
</script>
</body>
</html>
It sounds like what you are doing is having the user log into your page, and using this to get an auth token from a Google service. You're then turning this around and passing this back to the Assistant and calling this the Identity Flow.
While clever - this isn't the Identity Flow.
This is you using the Auth Code Flow to authenticate the user with Google, and then returning this token to Google and pretending this is an Identity Flow token. However, since you're using the Auth Code Flow, the auth tokens that you get back expire after an hour. (You can check out the lifetime in the information you get back from Google.)
If you are trying to do Account Linking and not manage anything yourself, you need to actually implement an OAuth server that proxies the Auth Code Flow requests from the Assistant to Google and the replies from Google back to the Assistant. While doable, this may be in violation of their policy, and isn't generally advised anyway.
Update to address some questions/issues in your comment.
using the Google Auth endpoints doesn't store the session either, so you'd still have to re-authenticate every hour
Since the Google Auth endpoints use the Auth Code Flow, you can use the offline mode to request a refresh token. Then, when an auth token expires, you can use the refresh token to get a new auth token. So you still have a long-term authorization for access and can get the short-term token to do the work you need.
Trying to shoehorn this into the Identity Flow, however, doesn't work. (And would be a really bad idea, even if it did.)
Can you provide some clarification on how to create an endpoint for the implicit flow?
Beyond the step-by-step description of what your OAuth server code can do in the Assistant documentation, I'm not sure what clarification you need. Your OAuth server fundamentally just needs to:
Be able to have a user:
Connect to an HTTPS URL
Authenticate themselves
Authorize the Assistant to contact your service on their behalf
Return a code by redirecting the user to Google's URL with a code in the parameter
And the Action webhook needs to be able to:
Accept this code as part of the request from the Assistant and
Figure out who the user is from this code. (ie - map the code to a user account in your system.)
There are a variety of ways you can do all of that. The OAuth server and Action could be on the same server or separate, but they at least need to have some agreement about what that code is and how that maps to your user accounts.
If your primary need is to access Google APIs on behalf of your user - then the user account that you have will likely store the OAuth tokens that you use to access Google's server. But you should logically think of that as separate from the code that the Assistant uses to access your server.
(As an aside - those steps are for the Identity Flow. The Auth Code Flow has a few more steps, but the fundamentals are similar. Especially on the Action side.)

Is it possible to use MSAL.js to get refresh token?

I want to integrate with Miscrosoft Outlook. I am able to login with MSAL.js and get an access token, but I am not able to get a refresh token. Is there a way to do it?
I'll assume that since you're using the MSAL.js (https://github.com/AzureAD/microsoft-authentication-library-for-js) that you're using implicit flow for authentication and authorization.
Implicit flow doesn't support refresh tokens, but you can request a new token silently. This is done similarly to how you request the token (id or access) in the first place. Unfortunately, I haven't found that MSAL.js does this transparently and I've needed to detect expired tokens and request the new tokens in my code. You can read more about refreshing tokens here.
Alternatively, if what you're implementing allows you to use one of the other MSAL libraries (for example, the .Net one) then you can use one of the other OAuth flows that explicitly support refresh tokens.
I couldn't find any answer in the MSAL.js documentation, however this source code comment suggests you can renew a token manually by passing only the clientId as your scope to acquireTokenSilent.
To renew idToken, please pass clientId as the only scope in the Authentication Parameters
I use msal v1.4.0
I remove 2 keys in storage (see picture) then call acquireTokenSilent again to get new access token.
Code to remove those 2 keys:
const keys = Object.keys(sessionStorage).filter(x => x.indexOf('authority') > 0)
keys.forEach(x => sessionStorage.removeItem(x))

Access token Facebook API Graph - public events

I have a general quation about the access token for Facebook API Graph.
I'm doing some testing with the API Graph explorer. I want to display some public facebook events on my website. https://graph.facebook.com/search?q=party&type=event
For the testing I created a access token and it's working.
Now my question: Can I use this token for my website or has it to be renewed all x-days? So when only searching for public events this token is good?
Thanks for your help.
Ben
You should be able to use an App Access Token, it is valid forever. It´s just a combination of App ID and App Secret, including a pipe sign: App-ID|App-Secret
Careful though, don´t use that token on the client.
More information:
https://developers.facebook.com/docs/facebook-login/access-tokens
http://www.devils-heaven.com/facebook-access-tokens/
Edit: I just tested it with an App Token, it seems that you do need a User Token after all. You can use an Extended User Token, it is valid for 60 days.