Setting up Client Credentials OAuth2 with ember-cli and ember-simple-auth - ember.js

I have been trying to setup OAuth2 client credentials flow with ember-cli and Rails API back-end and have hit a dead-end. Maybe because I'm new to ember. What I'm trying to do currently is this:
bower.json
{
"ember-simple-auth": "*"
}
Brocfile.js
app.import('vendor/ember-simple-auth/simple-auth.amd.js')
app.import('vendor/ember-simple-auth/simple-auth-oauth2.amd.js')
initializers/login.js
App.initializer({
name: 'Register Components',
initialize: function(container, application) {
registerComponents(container);
Ember.SimpleAuth.setup(application);
}
});
controllers/login.js
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Controller.extend(SimpleAuth.LoginControllerMixin, {
authenticatorFactory: 'simple-auth-authenticator:oauth2-password-grant'
});
templates/login.hbs
<form {{action authenticate on='submit'}}>
<label for="identification">Login</label>
{{view Ember.TextField id='identification' valueBinding='identification' placeholder='Enter Login'}}
<label for="password">Password</label>
{{view Ember.TextField id='password' type='password' valueBinding='password' placeholder='Enter Password'}}
<button type="submit">Login</button>
</form>
Any guides, tutorials or corrections in this regard is appreciated.

The latest release of Ember Simple Auth dropped the need for defining an initializer and added Ember CLI Addons for the library which make setting up everything a lot easier. Also the README and API docs now focus on using the library with Ember CLI which should help you a lot.
Checkout the README: https://github.com/simplabs/ember-simple-auth#readme

I recently discussed this on GitHub. This is what I ended up doing to authenticate the client using HTTP Basic Authentication (in app/app.js):
import OAuth2Authenticator from 'simple-auth-oauth2/authenticators/oauth2';
OAuth2Authenticator.reopen({
makeRequest: function(data) {
var clientId = MyProjectENV.APP.apiClientId;
var clientSecret = MyProjectENV.APP.apiClientSecret;
return Ember.$.ajax({
url: this.serverTokenEndpoint,
type: 'POST',
data: data,
dataType: 'json',
contentType: 'application/x-www-form-urlencoded',
headers: { "Authorization": "Basic " + btoa(clientId + ":" + clientSecret) }
});
}
});
and in config/environment.js:
var ENV = {
// ...
APP: {
apiClientId: "12345",
apiClientSecret: "abcdefg987654"
}

You can include the client id by setting the OAuth 2.0 authenticator's clientId property (see API docs: http://ember-simple-auth.com/api/classes/OAuth2PasswordGrantAuthenticator.html#property_clientId). Contrary to what other answer suggest here you should never ever include a client secret. As soon as you use a secret anywhere in your Ember app it's not a secret anymore as it is included in the source and visible for everyone who has access to the source (which usually is everybody on the whole internet).
Web clients are public clients in terms of OAuth that cannot be trusted, thus cannot use a client secret. You can use the client id for analytics etc. but you shouldn't even trust that on the server side as it could easily be manipulated and of course also used by other clients as it can simply be looked up from the app source.
So please everybody remember:
Never ever use a client secret in an Ember app!!!1!1!

Thanks to the huge effort of marcoow Ember-Simple-Auth supports an easy way of adding a client_it by now!
The value of clientId is set to null in the default authenticator by ESA (see file node_modules/e-s-a/addon/authenticator/oauth2-password-grant.js)
You can override the value in your custom authenticator in the same way you would override your custom server token endpoint.
// app/authenticators/oauth2.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
import ENV from '../config/environment';
export default OAuth2PasswordGrant.extend({
serverTokenEndpoint: `${ENV.api.host}/oauth/token`,
clientId: `${ENV.APP.apiClientId}`,
});
You might consider the authors opinion on setting the client_id in this github discussion on esa.
UPDATE:
I updated the source code and deleted the client secret since you should not include it an ember app.

Related

vuejs app with vue router hosted on amplify direct url to file doesn't work

I have an issue on which I struggled all day long, I have a VueJS app with vue router that I host on amplify?
everything working great Except that
I need to give a direct access to a file (I want to register an Apple merchant ID with stripe)
I tried to create a route in my route/index.js with my file name that redirect to a component that open the merchantid file with an windows.open('myfile').
it works great on local serve and build but not once deployed through amplify built with webpack
//router/index.js
import WellKnown from '#/components/AppleVerification.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/.well-known/apple-app-site-association',
component: WellKnown,
}
]
const router = new VueRouter({
mode: 'history',
routes
})
export default router
// AppleVerification.vue
<template>
<div></div>
</template>
<script>
export default {
name: 'WellKnown',
props: {
file: String
},
mounted () {
window.open('file:///.well-known/apple-developer-merchantid-domain-association')
}
}
</script>
so I went to amplify console and make a redirection with first priority to the URL and target address to the file. but it didn't work also.
I went out of ideas on how to give access to a file in my sources with a direct URL.
would appreciate a little help
thanks
You issue comes from the acces to the file througth Amplify for several reasons.
Try following:
rename your endfile "apple-developer-merchantid-domain-association"
with an extension like
apple-developer-merchantid-domain-association.txt
remove the dot in your path public/.well-known/apple-developer-merchantid-domain-association to public/well-known/apple-developer-merchantid-domain-association.txt
in your amplify console create a priority 1 rule that redirects your
https://mydomain/.well-known/apple-developer-merchantid-domain-association to
/well-known/apple-developer-merchantid-domain-association.txt with a 202 rexrite method.
It should work
You even didn't need the component anymore

How to use Amazon Cognito without Amplify

I'm just now diving into Cognito. The AWS setup has been fairly straight-forward, easy.
We have a variety of apps, webapps, and services and we'd like those to make use of the Cognito service. I've experience setting up similar with Auth0, but because we've been leveraging a number of Amazon Web Services, it really makes sense to use Cognito as well.
Everywhere I look, every guide eventually references Amplify client-side library and cli. We have existing apps and services, and really don't want to change tooling or import anything unnecessary to add bloat and complexity. Is there a way to use Cognito service without Amplify libraries? Is there a lightweight Cognito-only client library for interfacing with the Cognito service, authentication-and-authorization flow?
Update 03 Dec 2021
After re:Invent 2021, "Amplify Admin UI" was renamed to "Amplify Studio". With extra powers now:
automatically translates designs made in Figma to human-readable React UI component code
https://aws.amazon.com/blogs/mobile/aws-amplify-studio-figma-to-fullstack-react-app-with-minimal-programming/
===============
Original Answer
To start, I want to clarify that "Amplify" is an umbrella term for multiple things. We have:
Amplify Libraries (UI/JS)
Amplify CLI (to create cloud-native applications)
Amplify Console (ci/cd and hosting for full-stack web apps)
Amplify Admin UI (UI to create and configure full-stack web apps)
You can check the homepage for more clarification - https://docs.amplify.aws/
Is there a lightweight Cognito-only client library for interfacing with the Cognito service, authentication-and-authorization flow?
Behind the scenes, Amplify uses amazon-cognito-identity-js library to interface with Amazon Cognito. You can install that directly via npm install amazon-cognito-identity-js.
The source code has been moved to the Amplify Libraries (e.g. amplify-js) repository. Once again, is part of the "Amplify" umbrella under the first category "Amplify Libraries".
Is there a way to use Cognito service without Amplify libraries?
Another approach that you can do, is to use Amazon Cognito as an OAuth server. When you create an Amazon Cognito Hosted UI Domain, it provides you an OAuth 2.0 compliant authorization server.
You can create your own API/Backend for Signup/Login endpoints and exchange tokens/credentials with the Amazon Cognito OAuth server without using aws-sdk or any 3rd party dependency library.
I wrote a walkthrough example, how to configure your User Pool, endpoints that you need to talk to using Node.js, you can find it here: https://github.com/oieduardorabelo/node-amazon-cognito-oauth
You can follow the same idea for any other language.
As mentioned by #oieduardorabelo, you can simply install 'amazon-cognito-identity-js' where you can also find well done examples on npm.
Here is my test code to easily understand this lib. You must have already built the infrastructure on AWS (userPool, userClient and add a new user to test sign in - in my case the user has to change the password on first login so I added this use case on my script):
import * as AmazonCognitoIdentity from 'amazon-cognito-identity-js';
var authenticationData = {
Username: 'email',
Password: 'password',
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId: 'us-east-1_userpoolid',
ClientId: '26pjexamplejpkvt'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();
console.log(cognitoUser);
if (!cognitoUser) {
var userData = {
Username: authenticationData.Username,
Pool: userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
var accessToken = result.getAccessToken().getJwtToken();
var idToken = result.idToken.jwtToken;
console.log('Success', accessToken, idToken);
},
newPasswordRequired: function (userAttributes, requiredAttributes) {
delete userAttributes.email_verified;
cognitoUser.completeNewPasswordChallenge('DemoPassword1!', userAttributes, {
onSuccess: (data) => {
console.log(data);
},
onFailure: function (err) {
alert(err);
}
});
},
onFailure: function (err) {
alert(err);
},
});
}
If someone is interested in setup this test project from scratch run:
npm init -y
npm i -D webpack webpack-cli
npm i amazon-cognito-identity-js
in webpack.config.js:
var path = require('path');
module.exports = {
entry: './src/app.js',
mode: 'development',
output: {
path: path.resolve(__dirname, "dist"),
filename: 'main.js',
}
}
Create a new file in ./src/app.js where add the previous amazonCognitoIdentity code with the right AWS info ref and create ./dist/index.html whith:
...
<body>
<script src="main.js"></script>
</body>
in package.json add script "watch":
...
"scripts": {
"watch": "webpack --watch",
}
Finally run it:
npm run watch
and open the index.html directly on the browser with dev console as well.
Hopefully useful for someone.
As a result of research on the topic of using Amazon Cognito without Amplify in React, I came across such a sandbox. Switching from router 5 to router 6 probably won't be a problem. The main gold here is this hook. The rest of the implementation can be found in the sandbox: https://codesandbox.io/s/cognito-forked-f02htu
const Pool_Data = {
UserPoolId: "xxx",
ClientId: "yyy"
};
export default function useHandler() {
const [state, setstate] = useState({
loading: false,
isAuthenticated: false
});
const { loading, isAuthenticated } = state;
const userPool = new CognitoUserPool(Pool_Data);
const getAuthenticatedUser = useCallback(() => {
return userPool.getCurrentUser();
}, []);
console.log(getAuthenticatedUser());
useEffect(() => {
getAuthenticatedUser();
}, [getAuthenticatedUser]);
const signOut = () => {
return userPool.getCurrentUser()?.signOut();
};
console.log("I am here", getAuthenticatedUser()?.getUsername());
return {
loading,
isAuthenticated,
userPool,
getAuthenticatedUser,
signOut
};
}
I wrote an article a couple of years ago explaining how to do this.
The article talks about Amplify but as was mentioned in another response, that's more of an umbrella term, in the article we are using mostly UI components provided by the Amplify project.
you can find it here: https://medium.com/#mim3dot/aws-amplify-cognito-part-2-ui-components-935876fabad3

Send Angular PUT request to Django using Django CSRF

I am trying to update an entry into the django sqlite database using a put request. I am getting lots of 'forbidden' and '403' errors. I think this is because I can't find a way to attach the CSRF token from django.
I have seen some previous answers on here but they are from much older versions of Angular and I can't figure how to edit them to work with my code. (saying to put them in the module.config() block which I can't find).
Component HTML:
<button class="btn btn-warning shadow-sm" (click)="update(project)">Update</button>
Component TS:
update(project: Project) {
this.projectService.updateProject(project).subscribe();
}
Service TS:
updateProject(project: Project) {
var httpudpdate: any = this.http.put('/ph/projects/'+project.id, project)
return httpudpdate
}
I want the entry to be updated in the django but I am just getting errors, forbidden and 403.
Just import HttpClientXsrfModule to your project, it will take care of reading the cookie and resending it as a custom header in every request.
The cookie and header names are not a standard, but rather a convention, so you can configure them if the default ones don't match your backend's ones.
As it happens, Django's cookie name and header name don't match Angular default ones so HttpClientXsrfModule has to be imported withOptions like this:
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
#NgModule({
...
imports:[..., HttpClientXsrfModule.withOptions({ cookieName: 'csrftoken', headerName: 'X-CSRFToken' }), ...]
...
})
Import HttpClientXsrfModule into your app.module.ts
<!-- app.module.ts -->
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
imports:[...,HttpClientXsrfModule,...]
Inject HttpXsrfTokenExtractor into your service or HttpInterceptor or file in which you want to use cookie.
constructor(private cookieExtractor:HttpXsrfTokenExtractor){}
To get the cookie, for example xsrf token
const xsrf: string = this.cookieExtractor.getToken();

batch requests with credentials using React Apollo

How do you batch requests with credentials? I'm using an http-only JWT cookie and HttpLink allows me to pass a credentials: 'include' option which will forward the cookie through to my graphene server. When I try to switch to BatchHttpLink, it no longer accepts that option for configuration. Looking through the source, it doesn't appear there's an easy way to configure this. Anyone know how to handle this?
Here's how I was doing it without batching:
window['app-react'].GRAPHQL_URL = window['app-react'].GRAPHQL_URL || 'http://backend.app.local/graphiql'
const httpLink = new HttpLink({
uri: window['app-react'].GRAPHQL_URL,
credentials: 'include'
})
const client = new ApolloClient({
link: httpLink,
cache: new InMemoryCache(),
})
Here's how I wish it worked:
const batchHttpLink = new BatchHttpLink({
uri: window['joor-react'].GRAPHQL_URL,
credentials: 'include'
})
const client = new ApolloClient({
link: batchHttpLink,
cache: new InMemoryCache(),
})
When I do it this way though, the JWT cookie isn't passed in the header.
Right now apollo-link-batch-http is behind in its API compared to apollo-link-http. Here is a note from the docs (as of 2017-12-07)
Note: This package will be updated to remove the dependency on apollo-fetch an use the same options / API as the http-link
Using BatchHttpLink with apollo-fetch
The current API of apollo-link-batch-http requires a custom apollo-fetch if you want to make customizations to things like credentials. Here's a couple of options.
Provide a customFetch to createApolloFetch and define credentials in the fetch options.
Use apollo-fetch middleware
fetch.batchUse(({ options }, next) => {
options.credentials = 'include';
next();
});

csrf_token of Django into Vuejs when seperate them

I am using ajax request to send POST but it got response 403 because of csrf_token. I divide the frontend just using Vuejs and backend using Django to just reponse API only so I can't use Django template to render {% csrf_token %} or having csrftoken in session to use getcookie('csrftoken') like in Django's doc recommend. Is there anybody face this problem like me and got some solutions ? So thank you if you can help me this.
You can set the CSRF token in the header of your AJAX request. E.g., if you use jquery and jquery.cookie library, you can easily retrieve the Django-set csrftoken cookie like so:
$.ajax({
url : 'YOUR_URL_HERE',
headers: {'X-CSRFToken': $.cookie('csrftoken')},
type: 'POST',
dataType: 'json',
data: {},
success: function() {
},
error: function(xhr, errMsg, err) {
},
});
Django documentation also includes a section on this: https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax
Please note that this solution may depend on your specific Django settings. The Django documentation link above details everything pretty clearly.
EDIT:
Given that even your initial page request is not served by Django, here is how you can accomplish what you're looking for...
1.) Create a view in your Django app that manually generates and returns a CSRF token (using django.middleware.csrf.get_token):
def get_csrf_token(request):
token = django.middleware.csrf.get_token(request)
return JsonResponse({'token': token})
2.) You would also need to add an appropriate entry in your Django URLs file:
url(r'^get-token/$', get_csrf_token)
3.) Then your Vue.js app can fetch the CSRF token using this endpoint. This doesn't need to be a user-initiated event; for example, you can configure your front-end app to fetch it on the $(document).ready() event. Then, using your preferred AJAX library (I am using jQuery in my example):
$.ajax({
url: '/get-token/',
type: 'GET',
dataType: 'json',
success: function(data) {
$.cookie('csrftoken', data.token); // set the csrftoken cookie
}
});
4.) Now your csrftoken cookie is set and should be usable for subsequent POST requests.
$.ajax({
url : 'YOUR_URL_HERE',
headers: {'X-CSRFToken': $.cookie('csrftoken')},
type: 'POST',
dataType: 'json',
data: {},
success: function() {
},
error: function(xhr, errMsg, err) {
},
});
I have used jQuery for AJAX functionality and the jQuery.cookie library for getting and setting cookies, but of course you can use whichever library you would prefer for these functions.
According to the Django documentation you can simply use the ensure_csrf_cookie decorator on a view and that will send the cookie with the token with the response.
This is going to be wildly unpopular, but I've found it to be a relatively simple, secure and unobtrusive way to separate the front/backend.
In your VueJS app, you've probably got a login redirect when the user tries to access a page and are unauthenticated.
So instead of sending it to a vue router page, redirect it to /account/login/ (or some django app route - put an exception in cloudfront, or nginx proxy for /account/login/ to proxy pass to django) - then in the login.html template, just use a javascript window.location.href to your vueJS login page /login
The csrf_token will be set as a HttpOnly, secure cookie (which is what you want), and the disruption to the user is so minimal as to not even justify worrying about.