My application needs to signup users using Google plus and then display the user's profile data. When the user edits their profile on Google plus and visits my application again, the app needs to show the updated data. Hence the application needs to store the access_token and refresh_token for future use. I want to manage all this using google's js api without any server side google-api-client.
The problem is that I am unable to retrieve the refresh_token using the js api. I have setup a jsfiddle here. The google plus related code is as follows:
$('#signInButton').click(function () {
attributes = {
'accesstype': 'offline',
'clientid': "260932337012-1gdbsh3p7oknkjmeaa7m9q7e6nhhgd9c.apps.googleusercontent.com",
'scope': 'email profile',
'cookiepolicy': 'single_host_origin',
'approvalprompt': 'force',
'callback': signInCallback
};
gapi.auth.signIn(attributes);
});
function signInCallback(authResult) {
alert(authResult.request_token);
}
The refresh_token is not returned inspite of setting accesstype to offline. What am I doing wrong here?
Thanks in advance.
It's impossible to obtain a refresh token using purely the client-side flow with javascript browser based apps. This is a security feature when using the implicit grant type of OAuth 2.0.
The refresh token is for server-side or hybrid flows only.
With javascript only you'll simply need to signin the user again, or if they haven't signed out you can use the 'immediate': true parameter to authenticate them behind the scenes, i.e. without presenting them with another UI popup.
Related
Context:
I'm making an app that allows users to sign in with Google and which then makes calls to the Google Ads API on their behalf.
What works:
Users can sign in and grant the app the necessary permissions, and the API calls go through successfully.
What I'm struggling with:
I'd like to separate the login flow from the permissions flow. In other words, I would like users to be able to log in without granting the app any extraneous permissions, and then, later on, decide if they want to connect to their Google Ads account. This is when Google would prompt them for the necessary permissions.
Relevant code:
I'm using next-auth to handle authentication, setting up the Google provider like this:
./src/pages/api/[...nextauth].ts:
[…]
GoogleProvider({
clientId: env.GOOGLE_CLIENT_ID,
clientSecret: env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
scope: 'https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/adwords'
}
}
}),
[…]
You can also look at my reproduction repo, a skeletal app that does nothing but make an API call to Google Ads on behalf of the logged-in user. Bootstrapped with create-t3-app using Next.js, tRPC, Tailwind and Prisma.
UPDATE: I can wrap NextAuth(options) and take control of the endpoints
I've discovered that I can use custom initialization of the NextAuth API routes, which should provide me with an opportunity to alter the scope. Unfortunately, I'm still not sure how to provide an askForGoogleAdsPermissions flag to next-auth/react's signIn function in such a way that my customized API handler will be able to tell the difference and add or omit the authorization key in the Google Provider options accordingly.
Update #2: I can provide extra parameters to signIn
Turns out the signIn function can accept additional parameters as a third argument.
This allows me to separate the login from the permissions, but now the API calls I'm making to Google Ads are failing with "PERMISSION_DENIED: Request had insufficient authentication scopes.", despite having successfully granted permissions.
I can confirm the permissions are granted in my Google account, but when I look at the Prisma database managed by next-auth, I don't see the new scope in my account.
Perhaps I can use the signIn callback to make sure the scope is updated? My problem there is that the account I get as a parameter to my signIn callback has the old scope…
To separate the login and grant flows, I had to:
Include only the login scope in the NextAuth GoogleProvider.
Call the signIn method without arguments for a regular sign in
Call if with extra parameters to override the default scope when connecting to Google Ads: signIn('google', undefined, { scope: googleAdsScope })
Save the new scope to the database in the signIn callback
Make sure this isn't overwritten by a token refresh
See my repro repo for the full, working code.
Background
I'm implementing Alexa's App-to-App Account Linking Flow, and I'm stuck on Step 6 - enabling the skill using Alexa's Skill Activation API.
Concretely, I am not sure what value to supply to the redirect_uri POST field. In the docs, the following description is provided:
The redirect_uri parameter that was included in the authorization request to your OAuth 2.0 server to obtain the user's authorization code. This enables Amazon to retrieve access tokens from your token server. This URL must be opaque to Amazon.
My understanding is that Alexa wants to exchange an existing authorization code for an access token, but I don't know how Alexa is trying to accomplish this "under the hood" and my current approach throws a 400 error.
Error Message
[status] 400
[response] {"message":"Could not contact provider of account linking credentials"}
Notes
My app uses Firebase authentication, and creates accounts for users via federated login with Google and Facebook. Thus, Google and Facebook redirect back to my native app (React Native).
I do not have a universal link; instead in my account-linking flow,
the Alexa app redirects users to an html page that redirects to my app using its custom schema.
When a user signs into my app from Alexa, Alexa redirects them from my login page back to the Alexa app. In this case, the Alexa universal link is the redirect url.
When a user signs into Alexa from my app (app-to-app linking), The Alexa app redirects them to my app. My app is the redirect url.
I have tried using my app's [faux] "universal link" as the redirect url, to no avail. There are no other redirects in my login flows. What is this url supposed to be?
NB: I have a endpoint for exchanging an auth_code for an access_token. The token is returned in the body; there's no redirect with the access_token appended to the redirect_url.
Example Skill Activation (my React Native app):
async enableSkill() {
try {
let response = await fetch(`https://api.amazonalexa.com/v1/users/~current/skills/${this.skillId}/enablement`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.alexaAccessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
stage: 'development', // or live
accountLinkRequest: {
redirectUri: Linking.makeUrl(), // <--- unsure
authCode: this.myAppAuthCode, // <-- auth code from my system, not Alexa's
type: "AUTH_CODE"
}
})
});
return response.json();
} catch (err) {
throw new Error(err);
}
}
I think it is not possible to use different OAuth Server like Google and Facebook together. I am not sure if it is possible to use firebase as OAuth Server.
In the account linking tab of the skill, you have to enter the details of the OAuth server you want to use and in the accountLinkRequest you must enter the redirectUri which you used for the OAuth Login with this server.
When you have your own OAuth server make sure it is running on port 443. It took me hours to find out that it is not working with Port 3000 which my Node.js backend used.
According to the developer documentation The redirect_uri you are asking about is a parameter that was included in the authorization request to your OAuth 2.0 server to obtain the user's authorization code. This enables Amazon to retrieve access tokens from your token server. You must set this URL in the developer console of your skill like that:
I'm trying to get my head around setting up an OpenID Connect server for SSO authentication. I think my basic setup/requirements are pretty standard, but I'm having a little difficulty putting it all together.
The broad setup is a single page application, a web API, and an identity server. The SPA is served from the same domain name as the web API and the ID server is on a different domain, so I might have several SPA/Web API combinations, but of course every case is the same setup (single host with static content and an API). At the moment I'm working with IdentityServer4 to create the identity server; I'm flexible to trying other providers if there's some kind of problem with that one, but so far so good.
My login requirements are also pretty standard I think; I want to have short-lived access tokens and I also want to use refresh tokens to implement a sliding expiration so users don't have to be redirected off of my SPA until they've been inactive for "a while" (however I end up defining that).
After a bit of research, I think what I want is to use the authorization code flow. So generally, the way I thought this would work is:
A user visits the application host (that serves the web API and SPA); the static SPA is served
The SPA loads and determines that there is no access token in local storage. The SPA kicks off the login process by producing a random identifier and storing it in session storage, then navigates the browser to the ID server host
The user authenticates with the ID server host
The ID server hosts redirects to the client and includes in the redirect the random identifier the SPA originally generated along with an authorization code
Upon loading and detecting that it got an access code, the SPA checks session storage for the identifier stored in step 2. Finding it, the SPA calls the web API to exchange the authorization code for an access token
The web API uses a back channel with the ID server to produce an access token and refresh token
The web API stores the refresh token and access token then issues the access token to the client
In all future requests, the client uses the access token with the Web API. When the SPA determines that the access token it has is expired or about to expire, it request a refresh somehow (I'm going to hand-wave the refresh a bit for now)
So I went through the tutorial on the IdentityServer4 site, and to my surprise I ended up in a bit of a different state. It took me a while to work through it; the step I'm talking about if anyone wants to follow along is "Adding a JavaScript Client", but I'd be willing to be the result is common among people implementing OpenID Connect. The resulting flow differed from what I expected starting with step 5; instead of the SPA calling the web API with an authorization code and requesting an access token, the SPA uses CORS and makes a cross-domain request back to the ID server to request the access token. The tutorial didn't really cover refresh tokens all that much (there's other parts of the docs that do, but only briefly), but I think the implication is that if I wanted to use refresh tokens they'd be issued to the client and it would use local storage to store them; then for future refreshes it'd also do a cross-domain request back to the ID server. As a side note, another bit of surprise was that the tutorial has you use PKCE, which on research seems to be unnecessary for a web application; it's somewhat important as including a SHA-2 implementation client-side increases the size of my application by a fair bit.
I believe it is a bad practice to issue a refresh token to a web client and ask it to store it; I'm somewhat vague on the specific vulnerabilities that opens up, but the general idea is that if someone subverts your client somehow, a refresh token is considerably more powerful than a short-lived access token.
So, getting my head around this, I believe the way I originally though this would work was that the web API is the "Relying party" in OAuth 2 parlance, and the tutorial set it up so that the client is the "Relying party". It makes me think that if I want to get a sliding expiration, I have to go past where the tutorial went and move the functionality for token exchange from the client into the web API like I had originally envisioned. It would end up looking a bit like the web API functionally being a proxy for the SPA to exchange the authorization code for an access token.
Ultimately, my question is: am I getting this right? It looks like there are really two different models for implementing OpenID Connect for SPA/API web applications; one where the API is the RP, and another where the SPA is the RP. If you want to use refresh tokens, I think you should go with option 1, but maybe if you care that the API could impersonate the client you'd go with option 2? That still seems like it wouldn't matter to me; that authorization code/access token swap can only be used for a particular application, so it's not like one API could suddenly authenticate as a different backend in that setup. I'm just nervous about going off on my own to structurally alter the setup the tutorial had since this is security-related.
UPDATE
I used the authorization code flow instead of the implicit flow despite the accepted answer, since that's the most recent recommendation of the IETF (see https://datatracker.ietf.org/doc/html/draft-parecki-oauth-browser-based-apps-02#section-4, and a great writeup at https://brockallen.com/2019/01/03/the-state-of-the-implicit-flow-in-oauth2/). I accepted that answer because using a silent refresh via iframe instead of a refresh token seems to be the most standard approach for what I'm trying to do; using that I was able to build a working system that looks like the tutorial. In fact, the client library it recommends (oidc-client) has a built-in function to handle the details. For completeness, what I'm starting off with is this service:
import oidc from "oidc-client";
import Url from "url-parse";
let baseUrl = new Url(window.location.href).set("pathname", "").set("query", "").set("hash", "");
let redirectUrl = (new Url(baseUrl)).set("query", "redirect=fromIdentityProvider");
let silentRedirectUrl = (new Url(baseUrl)).set("pathname", "silent-refresh.html");
let identitySettings = {
authority: "[my application's id server domain]",
client_id: "[my client's id]",
redirect_uri: redirectUrl.toString(),
response_type: "code",
scope: "openid profile [my application's resource name]",
post_logout_redirect_uri: baseUrl,
automaticSilentRenew: true,
silent_redirect_uri: silentRedirectUrl.toString()
};
let userManager = new oidc.UserManager(identitySettings);
let user = null;
export default {
async logIn() {
await userManager.signinRedirect();
},
async isLoggedIn() {
return !!(await this.getAccessToken());
},
async logOut() {
await userManager.signoutRedirect();
},
async getAccessToken() {
user = await userManager.getUser();
return user ? user.access_token : null;
},
async initializeApp() {
let url = new Url(window.location.href, true);
if (url.query && url.query.redirect === "fromIdentityProvider") {
await new oidc.UserManager({
response_mode: "query"
}).signinRedirectCallback();
window.location = "/";
return false;
}
user = await userManager.getUser();
return true;
}
};
Then in my application I call initializeApp when the app starts and getAccessToken before any API calls. I still need to eventually add the ability to automatically redirect on 401 from the API, but that's pretty easy.
To make the silent redirect work, I created silent-redirect.html based on instructions here: https://www.scottbrady91.com/OpenID-Connect/Silent-Refresh-Refreshing-Access-Tokens-when-using-the-Implicit-Flow. I also integrated Google authentication as an external provider and verified that it also works for silent refreshes, so no trade-off there.
To round it out, for me the answer to my original question is basically "no", I don't want to move the exchange step to the backend. I did also decide to use PKCE even though it seems to me like it shouldn't be necessary, it's in the IETF recommendation I mentioned, so I'll stick with that.
There is a special OAuth2 flow for SPAs - the Implicit grant. If you want just an access token, specify &response_type=token when accessing the /auth endpoint. Alternatively, you can ask for an ID token as well with &response_type=token id_token&scope=openid. The SPA gets the token in the redirect URL from the autorization provider (in the hash part #access_token=...) along with its life-time expires_in=.... So the token stays in your browser - the hash part doesn't get sent to the server hosting the SPA files.
Your SPA should validate and keep both values and before the token expiration, it should call the /auth endpoint in an iframe with &prompt=none parameter. If your authorization provider supports Single Sign On (SSO), then you should get a fresh access token without the user noticing it. So it works similarly to a refresh token, without requiring CORS, PKCE or a client secret.
If you wanted to implement some more sophisticated SSO management, take a look at the OpenID Connect Session management RFC.
This question is for anyone who is familiar with
Node.js
Express
Passport
JWT Authentication with passport (JSON Web Tokens)
Facebook OAuth2.0 OR Google OAuth2.0
I have been doing some online courses and understand how to do the two following things:
Authentication using Passport Local Strategy + JWT Tokens
Authentication using Passport Google/Facebook Strategy + Cookie/sessions.
I am trying to combine the content from these two courses basically. I want to use Google Strategy + JWT Authentication. I want to use JWT instead of cookies because my app is going to be a web/mobile/tablet app, and I need to be accessing the api from different domains.
There are two issues I am having with this:
To kick off the Google/facebook OAuth pipelines, you need to call either '/auth/facebook' or '/auth/google'. Both Oauth flows work basically the same so when I say '/auth/google' from now on, I am referring to either. Now the issue I'm having is: On the client, do I call the '/auth/google' route with a href button link or an axios/ajax call? If I use the href or axios/ajax approach I am still getting problems with both solutions.
The href approach problem:
When I assign an <a> tag with a href to '/auth/google' the authentication works perfectly fine. The user gets pushed through the Google Auth flow, they log in and the '/auth/google/callback' route gets called. The problem I have now is how do I correctly send the JWT token back to the client from '/auth/google/callback'?
After a lot of googling I have seen that people have simply passed the the JWT back to the client from the oauth callback in the redirect query param. For example:
res.redirect(301, `/dashboard?token=${tokenForUser(req.user)}`);
The issue I have with this is that now the the ability to authenticate is saved in my browser history! I could log out (destroying the token saved in localStorage), and then simply look at my browser url history, go back to the url that contains the token in the query param, and I would automatically log in again without having to go through the Google Strategy! This is a huge security flaw and is obviously the incorrect way to approach it.
The axios/ajax approach problem:
Now before I explain the problem with this issue, I know for sure that If I get this working, it will solve all issues I was having with the previous href problem. If I manage to call '/google/auth' from an axios.get() call and receive the JWT in the response body, I will not be sending the token as url param, and it will not get saved in the browser history! Perfect right? well there is still some problems with this approach :(
When try to call axios.get('/auth/google') I get the following error:
How I've tried to solve the problem:
I installed cors to my npm server, and added app.use(cors()); to my index.js.
I took a stab and added "http://localhost:3000" to the "Authorised JavaScript origins" in Google developer console.
Neither of these solutions solved the issue, so now I really feel stuck. I want to use the axios/ajax approach, but I'm not sure how to get past this cors error.
Sorry for such a long message, but I really felt I had to give you all the information in order for you to properly help me.
Thanks again, looking forward to hear from you!
I solved this in this way:
On Front-End (can be mobile app) I made login request to Google (or Facebook) and after the user selected his account and logged in I got back response that contained google auth token and basic user info.
Then I sent that google auth token to backend where my API sent one more request to the Google API to confirm that token. (See step 5)
After successful request comes you get basic user info and e-mail. At this point, you can assume that user login via Google is good since google check returned that it's okay.
Then you just signup or login user with that email and create that JWT token.
Return token to your client and just use it for future requests.
I hope it helps. I implemented this multiple times and it showed like a good solution.
Though there is good answer, I wanted to add more information with example.
Passport's google/facebook strategy is session based, it stores user info in cookie which is not advisable. So we need to disable it first
To disable session we need modify our redirect router. For example if we have redirect path /google/redirect like following, we need to pass { session: false } object as parameter.
router.get('/google/redirect', passport.authenticate('google', { session: false }), (req, res)=> {
console.log(":::::::::: user in the redirect", req.user);
//GENERATE JWT TOKEN USING USER
res.send(TOKEN);
})
So where does this user come from? This user comes from passport's callback function. In the previous snippet we have added passport.authenticate(....) This middlewire initiates passport's google-strategy's callback which deals with the user. For example
passport.use(
new GoogleStrategy({
callbackURL: '/google/redirect',
clientID: YOUR_GOOGLE_CLIENT_ID
clientSecret: YOUR_GOOGLE_SECRET_KEY
},
(accessToken, refreshToken, profile, done)=>{
console.log('passport callback function fired');
// FETCH USER FROM DB, IF DOESN'T EXIST CREATE ONE
done(null, user);
})
)
That's it. We have successfully combined JWT and Google/Facebook Strategy.
The solution I found was to do the OAuth flow in a pop-up (window.open), that makes use of a pre-defined callback to pass the token to the front-end upon successful authentication.
Below are the relevant code samples, taken from this tutorial:
https://www.sitepoint.com/spa-social-login-google-facebook/
Here is the pre-defined callback and initial open method, called from your front-end:
window.authenticateCallback = function(token) {
accessToken = token;
};
window.open('/api/authentication/' + provider + '/start');
And here is what your OAuth Callback URL should return, upon successful authentication (which is the last step/page inside your pop-up):
<!-- src/public/authenticated.html -->
<!DOCTYPE html>
<html>
<head>
<title>Authenticated</title>
</head>
<body>
Authenticated successfully.
<script type="text/javascript">
window.opener.authenticateCallback('{{token}}');
window.close();
</script>
</body>
</html>
Your token would now be available to your front-end's pre-defined callback function, where you could easily save it in localStorage.
I suppose though, you could do the OAuth flow in the same window then (sans pop-up) and return an HTML page (similar to the above) that just saves the token and redirects the user to a dashboard immediately.
If your front-end domain was different from your api/auth server, however, you would probably need to redirect from your api/auth server to your front-end with a single-use, time-sensitive token (generated by your api/auth server), that your front-end could then use to call and receive (with axios) your actual token. This way you wouldn't have that browser history security problem.
I figured out how to get the sign in button and redirect through php code. BUT, Chrome Dev only allows client side code. How do I get the log in with twitter with client side code for my Chrome app?
is there a way to run php code for a chrome app?
Here is another alternative to CodeBird for authenticating a twitter user in a Chrome extension.
The key with this approach is to provide Twitter with a legitimate domain for the callback URL for your app. Then, use content scripts to inject a script onto that same domain. That script will parse the query string of the URL to get the tokens and send those tokens in a message to your extension's background script. Your background script will take the tokens and then perform the third leg of the oauth process, which will finally get you the oauth token and oauth token secret.
Here is a brief example:
in your manifest.json, make sure your content script matches the same domain that you put in your twitter app settings callback URL:
"content_scripts": [{
"matches": ["https://andyjiang.com/*"],
"js": ["js/session.js"]
}]
Then, in your js/session.js file, have this sort of logic:
chrome.runtime.sendMessage({type: 'auth', session:
window.location.search.substr(1)}, function(response) {
window.open('', '_self', '');
window.close();
});
In your background script, have some logic that listens for the message, gets the token, and uses Twitter's API for the third leg of the oauth process to finally get the oauth token and oauth token secret, which you can then save in localStorage or chrome.storage.
Here is sample code of the logic:
https://github.com/lambtron/chrome-extension-twitter-oauth-example
Hope that helps!
You can use the Chrome Identity API for this. Check out Non-Google account authentication for simple instructions on making a request using the launchWebAuthFlow API function.
Previously, there were client side libraries for implementing the OAuth flow, such as oauth2-extensions described here, but thankfully this is not required anymore .
Update
I've been playing around trying to get an example working for Twitter, but haven't quite got there. It appears that Twitter doesn't have an API endpoint that matches the OAuth2 URL that is expected. I think in the case of Twitter, you may have to use OAuth 1.0a instead, which would require a library after all. I found one called CodeBird. I will try and investigate further though.
Example using Chrome Identity API to Authorise Instagram
You need to register the client to your provider with https://abcdefghijklmnopqrstuvwxyzabcdef.chromiumapp.org/intagram_cb, where 'abcdefghijklmnopqrstuvwxyzabcdef' is replaced with your extension ID and intagram_cb is name for a path to be able to distinguish between other providers you wish to authenticate with within the extension. If you only have one, then you can omit it.
Add provider to the permissions property in the manifest.json file:
"permissions": [
"*://*.instagram.com/*"
]
Get access token. You obtain the client_id token from your provider account:
var redirect_uri = chrome.identity.getRedirectURL("intagram_cb");
var client_id = "123456789012345";
var auth_url = "https://instagram.com/oauth/authorize/?" +
"client_id=" + client_id + "&" +
"response_type=token&" +
"redirect_uri=" + encodeURIComponent(redirect_uri);
chrome.identity.launchWebAuthFlow({'url':auth_url, 'interactive': true},
function(redirect_url) {
// extract the token from this url and use it for future requests
var accessToken = redirect_url.substring(redirect_url.indexOf("=") + 1);
}
});