I cannot authenticate to a FeathersJS server using OAuth2 Facebook strategy because after Facebook grants access to user profile, the feathers-authentication-oauth2 plugin doesn't create the user into the DB and it also doesn't create the required JWT token to be authenticated when calling feathersclient.authenticate() in the client app.
I've tried to follow all documents I've found that explain how to do it, but as a good example I could select this one (https://blog.feathersjs.com/how-to-setup-oauth-flow-with-featherjs-522bdecb10a8) that is very well explained.
As a starting point I've taken the Feathers chat application explained at the documentation (https://docs.feathersjs.com/guides/chat/readme.html) after having it working properly, I've added tha OAuth2 part as explained in the Medium document. In the default.json file I've added the "facebook" authentication strategy:
"facebook": {
"clientID": "MY_CLIENT_ID",
"clientSecret": "MY_CLIENT_SECRET"
}
In the authentication.js fileI've added the configuration of the Facebook OAuth2 authentication:
const authentication = require('#feathersjs/authentication');
const jwt = require('#feathersjs/authentication-jwt');
const oauth2 = require('#feathersjs/authentication-oauth2');
const FacebookStrategy = require('passport-facebook').Strategy;
module.exports = function (app) {
const config = app.get('authentication');
// Set up authentication with the secret
app.configure(authentication(config));
app.configure(jwt());
app.configure(oauth2({
name: 'facebook',
Strategy: FacebookStrategy,
callbackURL: '/',
scope: ['public_profile', 'email'],
}));
...
And finally, in src/app.js file I've added a new "Facebook login" button that just changes window.location to '/auth/facebook' so that the OAuth2 Facebook process can begin.
After pressing the "Facebook login", I'd expect the user to be created in the NeDB DB and a valid JWT to be stored so that the feathersclient.authenticate() call would not fail.
But instead of that, the Facebook login page is properly called, and after that the browser is returned to the main page ('/'), but after that, when the main page is reloaded and the feathersclient.authenticate() is called, the server complains that there isn't any valid JWT token, so authentication fails. Also I cannot see the user created in the NeDB DB, so the supposed user and JWT creation that should be done by the feathers-authentication-oauth2 plugin is not...
I've finally made it work... I was wrongly configuring the Facebook authentication strategy, I've changed it to:
app.configure(oauth2({
name: 'facebook',
successRedirect: '/',
failureRedirect: '/',
Strategy: FacebookStrategy
}));
and now it is working.
Related
Created a user pool client using Cognito Identity Provider Client SDK for JavaScript v3
npm install #aws-sdk/client-cognito-identity-provider.
The following code shows how I created the resources server and the user pool client, using the mentioned👆 SDK...
let poolName = 'UserPool';
const client =new CognitoIdentityProviderClient({
region: process.env.COGNITO_AWS_REGION
});
// create resource server
const createResourceServerCommand = new CreateResourceServerCommand({
Name: poolName,
UserPoolId: UserPool.Id,
Identifier: 'https://localhost:8080/api/v2',
Scopes: [
{
ScopeName: 'access',
ScopeDescription: 'General access to API'
}
]
});
const { ResourceServer } = await client.send(createResourceServerCommand);
// create the user pool client
const createUserPoolClientCommand = new CreateUserPoolClientCommand({
ClientName: 'Default',
UserPoolId: UserPool.Id,
ExplicitAuthFlows: ['USER_PASSWORD_AUTH'],
GenerateSecret: true,
AllowedOAuthFlows: ['client_credentials'],
SupportedIdentityProviders: ['COGNITO'],
AllowedOAuthScopes: [ 'https://localhost:8080/api/v2/access' ]
});
const { UserPoolClient } = await client.send(createUserPoolClientCommand);
...but, I can't fetch tokens using the grant type client_credentials. Therefore getting the following error.
{
"error": "invalid_grant"
}
However, if I use AWS console to navigate to the user pool > Client > Edit the hosted UI and click on the save button without making any changes...
... I am able to fetch a token using the client_credentials grant type.
Is there any setting that I might be missing in the above code that AWS console is setting? I need the following code to automate the creation of user pools.
When I switched to the old I noticed this notification
Apparently, Oauth flows are not enabled by default. Hence adding the following attribute to the CreateUserPoolClientCommandInput object AllowedOAuthFlowsUserPoolClient: true enables it. Hope this helps some newbie like me out there.
Question
How would we test a Doorkeeper oauth2 implementation for a Zapier cli app?
Background
I have a Rails 3 app. I am trying to create a Zapier client for the application and I decided to use OAuth. Thus I configured doorkeeper to generate a JWT. All looks good, I am able to authorize and get token using the redirects.
I am not sure how to test the app purely through the console. Wouldn't it require some way to authorize using username/password?
I got an app generated from the template with some minor differences.
it('can fetch an access token', (done) => {
const bundle = {
inputData: {
// In production, Zapier passes along whatever code your API set in the query params when it redirects
// the user's browser to the `redirect_uri`
code: 'one_time_code',
subdomain: 'ducks'
},
environment: {
CLIENT_ID: process.env.CLIENT_ID,
CLIENT_SECRET: process.env.CLIENT_SECRET
}
};
appTester(App.authentication.oauth2Config.getAccessToken, bundle)
.then((result) => {
result.access_token.should.eql('a_token');
result.refresh_token.should.eql('a_refresh_token');
done();
})
.catch(done);
});
results in something like this:
1) oauth2 app can fetch an access token:
Got 401 calling POST https://ducks.<domain>.com/oauth/token, triggering auth refresh.
What happened:
Starting POST request to https://ducks.<domain>.com/oauth/token
Received 401 code from https://ducks.<domain>.com/oauth/token after 1425ms
Received content "{"error":"invalid_request","error_description":"The request is missing a required parameter, include"
Got 401 calling POST https://ducks.<domain>.com/oauth/token, triggering auth refresh.
Which should be because the user is not logged in the request made in the test console...
How can I make the user login? Or should the tests be changed?
I have a mobile app communicates with a server. For authentication in the mobile app, I'm using sign in with google. The sign in returns an accessToken which I send to my server and verify using google-auth-library as suggested here: https://developers.google.com/identity/sign-in/web/backend-auth
import GoogleAuth from 'google-auth-library'
const auth = new GoogleAuth()
const client = new auth.OAuth2(MyClientId, '', '')
apiRoutes.use((req, res, next) => {
// get the token from the request
const token = req.token
if (token) {
// verify secret with google
client.verifyIdToken(token, MyClientId, (err, payload) =>
// proceed with the user authenticated
...
Is it necessary to make this call with every request that the user makes? Would it be good practice to do some sort of caching? Or to have my own implementation of JWT on my server that includes the google payload?
No, the server should usually creates an account for the user once it validates the access token, saving the Google ID in the database along other user details (ID, email, name etc), and then returns an access token to the mobile application.
Once the latter (usually stored locally) expires, it can be refreshed without prompting the user for permission.
I'm trying to implement a Grails app that provides OAuth2 using the Spring Security OAuth2 Provider plugin. The provider app and a client app that I use to test it, are both available on GitHub.
I've followed the instructions in the plugin's docs, that explain how to implement a provider. To test it, I saved the following oauth client and user in Bootstrap.groovy
def init = { servletContext ->
def saveArgs = [flush: true, failOnError: true]
def userRole = new Role(authority: 'ROLE_USER').save(saveArgs)
def clientRole = new Role(authority: 'ROLE_CLIENT').save(saveArgs)
// create an OAuth client
new Client(
clientId: 'my-client',
authorizedGrantTypes: ['authorization_code', 'refresh_token', 'implicit', 'password', 'client_credentials'],
authorities: ['ROLE_CLIENT'],
scopes: ['read', 'write'],
redirectUris: ['http://localhost:9090/oauth-client/auth/callback']
).save(saveArgs)
// create a regular user
def user = new User(username: 'me', password: 'password').save(saveArgs)
UserRole.create user, userRole, true
}
In the client app, I click on the following link that initiates the authorization code grant flow
<g:set var="redirectUrl" value="${g.createLink(controller: 'auth', action: 'callback', absolute: true)}"/>
<h2>
OAuth Login
</h2>
I enter the username and password of the above user in the login form, then click the "Authorize" button shown in the confirmation dialog thereafter. An authorization code is successfully returned to the client app, but when it tries to exchange this for an access token the following error occurs
invalid_scope: Empty scope (either the client or the user is not allowed the requested scopes)
The code used to exchange the authorization code for an access token is shown below.
String getAccessToken(String authCode) {
def url = 'http://localhost:8080/oauth2-provider/oauth/token'
def params = [
grant_type: 'authorization_code',
code: authCode,
client_id: 'my-client'
]
new HTTPBuilder(url).request(POST, JSON) {
uri.query = params
response.success = { resp, json ->
json.access_token
}
response.failure = { resp, json ->
log.error "HTTP error code: $resp.status, status line: $resp.statusLine, "
json.each { key, value ->
log.error "$key: $value"
}
}
}
}
The link that initiates the OAuth process requests access to the read scope. This is included in the scopes property of the Client object, so there's no obvious reason why access to this scope should be prohibited.
You can reproduce the error by following these instructions:
Start the provider app with grails run-app
Start the client app on port 9090 with grails run-app -Dserver.port=9090
On the homepage of the client app click the "OAuth Login" link. This redirects to the provider app and prompts you to login. Enter the username "me", password "password", and click the "Authorize" button in the confirmation dialog that follows.
For anyone who stumbles across this in the future:
This was the result of a small bug in the plugin. The temporary workaround is to include the scope parameter in the token endpoint request with the same value as the scope parameter sent to the authorization endpoint.
See issue #64 on the plugin's GitHub repository for more details about the problem.
I have setup my facebook auth per passportjs docs:
var passport = require('passport')
, FacebookStrategy = require('passport-facebook').Strategy;
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://www.example.com/facebook/callback"
},
function(accessToken, refreshToken, profile, done) { ... });
}
));
app.get('/login/facebook', passport.authenticate('facebook'))
.get('/facebook/callback', passport.authenticate('facebook', {successRedirect: '/', failureRedirect: '/login'}));
All this works fine. However, there are cases (such as token expiration) when I want to automatically redirect the user to the page that the user was on before initiating the login request. So I tried to plumb a query string param through the login request (from client to server to facebook and back). But I cant see a way to specify that in the callbackURL.
Furthermore, when I tried hard-coding some context param to the config callbackURL (eg: "http://www.example.com/facebook/callback?redir=lastUserPage") I get an OAuth parse error. Interestingly enough, Facebook does respond correctly with the access code as well as the redir param, but it fails with OAUTH exception:
FacebookTokenError: Error validating verification code. Please make sure your redirect_uri is identical to the one you used in the OAuth dialog request
at Strategy.parseErrorResponse (C:\Sources\node_modules\passport-facebook\lib\strategy.js:198:12)
at Strategy.OAuth2Strategy._createOAuthError (C:\Sources\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:345:16)
at C:\Sources\node_modules\passport-facebook\node_modules\passport-oauth2\lib\strategy.js:171:43
at C:\Sources\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:177:18
at passBackControl (C:\Sources\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:124:9)
at IncomingMessage.<anonymous> (C:\Sources\node_modules\passport-facebook\node_modules\passport-oauth2\node_modules\oauth\lib\oauth2.js:143:7)
at IncomingMessage.emit (events.js:117:20)
at _stream_readable.js:943:16
at process._tickCallback (node.js:419:13)
Note that I had this working using WIF before. I don't see any security concerns with passing additional query string parameters through the OAuth process..
Any idea how I can get past this?
I'm not sure how to do what you're asking, but for your desired end goal you could:
Save a cookie before authenticating
Authenticate the user
on the resulting callback page, check for the cookie and redirect if present.
Wouldn't this work just as easily?