I have to subscribe to cometD Salesforce channel and hence building cometD client in python. I am using the below python library.
https://github.com/dkmadigan/python-bayeux-client
And below is the handshake response I am getting
{'Host': ['xxxxx.my.salesforce.com/cometd/42.0/'], 'Content-Type': ['application/x-www-form-urlencoded'], 'Authorization': ['admin#123Pi6s9Y2QVergfergregpqqY']} message={"channel":"/meta/handshake","id":"1",
"supportedConnectionTypes":["callback-polling", "long-polling"],
"version":"1.0","minimumVersion":"1.0"} Headers({'host': ['xxxxx.my.salesforce.com/cometd/42.0/'], 'content-type': ['application/x-www-form-urlencoded'], 'authorization': ['admin#123Pi6s9Y2QVergfergregpqqY']}) {u'successful': False, u'advice': {u'reconnect': u'none'}, u'ext': {u'replay': True, u'sfdc': {u'failureReason': u'401::Request requires authentication'}, u'payload.format': True}, u'error': u'403::Handshake denied', u'id': u'1', u'channel': u'/meta/handshake'}
And I am getting 401::Request requires authentication.
In the Authorization key, I have concatenated password and Access token i.e. admin#123Pi6s9Y2QVergfergregpqqY where admin#123 is the password I use to login to Salesforce.
I have been banging my head since 2 days but not able to figure out why handshake is failing. Any suggestions?
I believe that the authorization key is incorrect. It is not your password that is expected but an OAuth access token or session id that you receive after you log into salesforce. See the different OAuth flows, if you are testing you can use the username password flow.
The following method u can use to get the session id when needed
import requests
import json
LOGIN_INSTANCE_URL = 'https://test.salesforce.com/services/oauth2/token'
LOGIN_USER_NAME = 'username_here'
CLIENT_ID = 'connected app consumer key'
CLIENT_SECRET = 'connected app consumer secret'
PASSWORD = 'password token'
def connect(authUrl, clientId, secret, username, password):
headers = {
}
postBody = {
'grant_type': 'password',
'client_id': clientId,
'client_secret':secret,
'username': username,
'password': password
}
try:
response = requests.post(authUrl, data = postBody, headers = headers)
#response.raise_for_status()
if (response.status_code == 200):
authResponse = response.json()
return authResponse['access_token']
else: #if not 200 see what the problem was
print response.text
except requests.exceptions.RequestException as e:
print e
print(connect(LOGIN_INSTANCE_URL, CLIENT_ID, CLIENT_SECRET, LOGIN_USER_NAME, PASSWORD))
This is just sample code that should work, but you need to create a connected app first. for stand alone app without user intervention JWT flow is better.
Related
I must be really stupid, But I have been struggling for weeks to try solve this issue, and all the digging I have done (in Stack overflow and MS Documentation) has yielded no results (or I'm too stupid to implement auth correctly)
I have a dotnet service which needs to act as an API - both for an application to post data to (an exe which logs exception data), and for a UI (react app) to get the posted exceptions
the exe can successfully send data to the dotnet app after first getting a token from login.microsoftonline.com and then sending the token (and secret) in the http request.
A sample postman pre-request script of the auth used (I've set all the secret stuff as environment variables):
pm.sendRequest({
url: 'https://login.microsoftonline.com/' + pm.environment.get("tenantId") + '/oauth2/v2.0/token',
method: 'POST',
header: 'Content-Type: application/x-www-form-urlencoded',
body: {
mode: 'urlencoded',
urlencoded: [
{key: "grant_type", value: "client_credentials", disabled: false},
{key: "client_id", value: pm.environment.get("clientId"), disabled: false},
{key: "client_secret", value: pm.environment.get("clientSecret"), disabled: false}, //if I don't configure a secret, and omit this, the requests fail (Azure Integration Assistant recommends that you do not configure credentials/secrets, but does not provide clear documentation as to why, or how to use a daemon api without it)
{key: "scope", value: pm.environment.get("scope"), disabled: false}
]
}
}, function (err, res) {
const token = 'Bearer ' + res.json().access_token;
pm.request.headers.add(token, "Authorization");
});
Now in React, I am using MSAL(#azure/msal-browser) in order to login a user, get their token, and pass the token to one of the dotnet endpoints using axios as my http wrapper, but no matter what I do, it returns http status 401 with WWW-Authenticate: Bearer error="invalid_token", error_description="The signature is invalid".
A simplified code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent(activeAccount).accessToken;
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401
The dotnet service has the following configurations
public void ConfigureServices(IServiceCollection services){
...
var authScheme = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
authScheme.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"));
...
}
namespace Controllers{
public class EndpointController : ControllerBase{
...
[Authorize]
[HttpGet]
public IActionResult GetEndpoint(){
return Ok("you finally got through");
}
}
}
I've literally tried so many things that I've lost track of what I've done...
I've even cried myself to sleep over this - but that yielded no results
i can confirm that running the request in postman, with the pre request script, it is possible to get the response from the endpoint
So....
After much digging and A-B Testing I was able to solve this issue.
I discovered that I was not sending the API scope to the OAuth token endpoint. To do this I needed to change the input for acquireTokenSilent.
The updated code flow to login user and request data from the API:
import {publicClientApplication} from "../../components/Auth/Microsoft";//a preconfigured instance of PublicClientApplication from #azure/msal-browser
const data = await publicClientApplication.loginPopup();
// ... some data validation
publicClientApplication.setActiveAccount(data.account);
// .. some time and other processes may happen here so we don't access token directly from loginPopup()
const activeAccout = publicClientApplication.getActiveAccount();
const token = publicClientApplication.acquireTokenSilent({scopes:["api://XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/.default"],account:activeAccount}).accessToken;//here scopes is an array of strings, Where I used the api URI , but you could directly use a scope name like User.Read if you had it configured
const endpointData = await api()/*an instance of Axios.create() with some pre-configuration*/.get(
'/endpoint',
{ headers: {'Authorization': `bearer ${token}`} }); // returns status 401
I'm currently doing forgot password functionality for the first time and here's the code so far.
sends the email for the user that has the URL with the JWT token
router.post('/change-password', verifyAuth, resetPassword);
receives and confirms JWT then changes password
router.post('/change-password/:token/:password', confirmResetPassword);
the process I'm currently thinking about is in the email I send the user to
http://localhost:3000/change-passowrd?token=TOKEN_VALUE
but I'm not sure if this is a smart idea or not? I can also use cookies if it's better, any idea?
It's okay to store the JWT token store in the URL for reset password functionality. You have to send this link using Email or any other secure communication service.
I implemented this feature
https://yourapp.com/home/reset/${token}
const data = {
from: "yourcompanymail#outlook.com",
to: user.email,
subject: "Please reset your password",
text: `Hello ${user.name},\n\nI heard that you lost your Teeny password. You can use the following link to reset your password: https://yourapp.com/home/reset/${token}
};
transporter.sendMail(data, function (error, info) {
if (error) {
console.log(error);
} else {
console.log("Email sent: " + info.response);
}
});
Now if the user hits this URL, validate the token and redirect or render the change password page. But don't send the password through the URL.
I have implemented JWT for user login in my app (before Spotify Auth), like so:
Flask
#auth_blueprint.route('/auth/login', methods=['POST'])
def login_user():
# get post data
post_data = request.get_json()
response_object = {
'status': 'fail',
'message': 'Invalid payload.'
}
if not post_data:
return jsonify(response_object), 400
email = post_data.get('email')
password = post_data.get('password')
try:
# fetch the user data
user = User.query.filter_by(email=email).first()
if user and bcrypt.check_password_hash(user.password, password):
auth_token = user.encode_auth_token(user.id)
if auth_token:
response_object['status'] = 'success'
response_object['message'] = 'Successfully logged in.'
response_object['auth_token'] = auth_token.decode()
return jsonify(response_object), 200
else:
response_object['message'] = 'User does not exist.'
return jsonify(response_object), 404
except Exception:
response_object['message'] = 'Try again.'
return jsonify(response_object), 500
These are the methods of my SQLAlchemy User(db.Model)
def encode_auth_token(self, user_id):
"""Generates the auth token"""
try:
payload = {
'exp': datetime.datetime.utcnow() + datetime.timedelta(
days=current_app.config.get('TOKEN_EXPIRATION_DAYS'),
seconds=current_app.config.get('TOKEN_EXPIRATION_SECONDS')
),
'iat': datetime.datetime.utcnow(),
'sub': user_id
}
return jwt.encode(
payload,
current_app.config.get('SECRET_KEY'),
algorithm='HS256'
)
except Exception as e:
return e
#staticmethod
def decode_auth_token(auth_token):
"""
Decodes the auth token - :param auth_token: - :return: integer|string
"""
try:
payload = jwt.decode(
auth_token, current_app.config.get('SECRET_KEY'))
return payload['sub']
except jwt.ExpiredSignatureError:
return 'Signature expired. Please log in again.'
except jwt.InvalidTokenError:
return 'Invalid token. Please log in again.'
React
App.jsx
loginUser(token) {
window.localStorage.setItem('authToken', token);
this.setState({ isAuthenticated: true });
this.getUsers();
this.createMessage('Welcome', 'success');
};
(...)
<Route exact path='/login' render={() => (
<Form
isAuthenticated={this.state.isAuthenticated}
loginUser={this.loginUser}
/>
)} />
and
Form.jsx
handleUserFormSubmit(event) {
event.preventDefault();
const data = {
email: this.state.formData.email,
password: this.state.formData.password
};
const url = `${process.env.REACT_APP_WEB_SERVICE_URL}/auth/${formType.toLowerCase()}`;
axios.post(url, data)
.then((res) => {
this.props.loginUser(res.data.auth_token);
})
Third Party Authorization + Second App Authentication
Now I'd like to add a second layer of authentication and handle tokens after Spotify callback, like so:
#spotify_auth_bp.route("/callback", methods=['GET', 'POST'])
def spotify_callback():
# Auth Step 4: Requests refresh and access tokens
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIPY_CLIENT_ID')
CLIENT_SECRET = os.environ.get('SPOTIPY_CLIENT_SECRET')
REDIRECT_URI = os.environ.get('SPOTIPY_REDIRECT_URI')
auth_token = request.args['code']
code_payload = {
"grant_type": "authorization_code",
"code": auth_token,
"redirect_uri": REDIRECT_URI,
'client_id': CLIENT_ID,
'client_secret': CLIENT_SECRET,
}
post_request = requests.post(SPOTIFY_TOKEN_URL, data=code_payload)
# Auth Step 5: Tokens are Returned to Application
response_data = json.loads(post_request.text)
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
token_type = response_data["token_type"]
expires_in = response_data["expires_in"]
# At this point, there is to generate a custom token for the frontend
# Either a self-contained signed JWT or a random token?
# In case the token is not a JWT, it should be stored in the session (in case of a stateful API)
# or in the database (in case of a stateless API)
# In case of a JWT, the authenticity can be tested by the backend with the signature so it doesn't need to be stored at all?
res = make_response(redirect('http://localhost/about', code=302))
return res
Note: this a possible endpoint for getting new Spotify tokens:
#spotify_auth_bp.route("/refresh_token", methods=['GET', 'POST'])
def refresh_token():
SPOTIFY_TOKEN_URL = "https://accounts.spotify.com/api/token"
CLIENT_ID = os.environ.get('SPOTIPY_CLIENT_ID')
CLIENT_SECRET = os.environ.get('SPOTIPY_CLIENT_SECRET')
code_payload = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
}
encode = 'application/x-www-form-urlencoded'
auth = base64.b64encode("{}:{}".format(CLIENT_ID, CLIENT_SECRET).encode())
headers = {"Content-Type" : encode, "Authorization" : "Basic {}".format(auth)}
post_request = requests.post(SPOTIFY_TOKEN_URL, data=code_payload, headers=headers)
response_data = json.loads(post_request.text)
access_token = response_data["access_token"]
refresh_token = response_data["refresh_token"]
token_type = response_data["token_type"]
expires_in = response_data["expires_in"]
return access_token
What is the best way of handling my tokens after Spotify callback?
Considering that, once user is logged with the app, he will also be logged with Spotify non-stop, having to refresh Spotify's access token every 60 minutes:
Is Authorization Code a server-to-server flow only to protect secret app credentials, and then it is safe to have tokens at frontend?
Should I keep both Access token and refresh tokens stored at frontend, and have a Stateless JWT?
Should I keep only temporary access token and keep refresh tokens at database, having a Stateful JWT?
Should I opt for a Session, persisted only server-side, instead?
What is the safest way of handling my sensitive data here? And, considering the code above, how so?
A huge number of questions here! Let's take them one by one:
Is Authorization Code a server-to-server flow only to protect secret app credentials, and then it is safe to have tokens at frontend?
In the Authorization Code grant, you have to exchange the Authorization Code for a token. This is done with a request to /token (grant_type: authorization_code) and it requires your client_id and client_secret which is secretly stored in your server (aka not-public in your react web app). In this context it's indeed server-to-server.
Should I keep both Access token and refresh tokens stored at frontend, and have a Stateless JWT?
In your case, I would say no. If the token will be used to do some API request to Spotify on server-side, please keep access_token and refresh_token server-side.
But then, it's not anymore stateless ? Indeed.
What could you do "stateless" ?
If you really want/need stateless tokens, IMHO you could store the access_token in a Cookie with following options (and it's mandatory):
Secure: cookies only sent on HTTPS
HttpOnly: not accessible from Javascript
SameSite: preferrably strict! (here it depends if you need CORS)
PRO:
It's stateless
CON:
It might be a huge cookie.
Anyone which access your computer can get the access_token, just like a session cookie. Expiration time is important here. See also: https://stackoverflow.com/a/41076836/2437450
Something else ???? To be challenged.
The case of refresh_token.
I would recommend to store refresh tokens server-side because it's usually a long-life token.
What to do when the access_token expire ?
When a request comes with an expired access_token, you can simply refresh the access_token with server-side-stored refresh_token, do the job, and return the response with a new access_token stored through Set-Cookie header.
Additional note about JWT
If you always have JWT and you store them in Http-Only cookies, you'll probably say that you don't have any way to know if your are logged-in from your React app.
Well there is a trick I already experimented with JWT which is pretty nice.
A JWT is composed of 3 parts; the header, the payload and the signature. What you actually want to protect in your cookies is the signature. Indeed, if you don't have the right signature the JWT is useless. So what you could do is to split the JWT and make only the signature Http-Only.
In your case it should look like:
#app.route('/callback')
def callback():
# (...)
access_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJSYXBoYWVsIE1lZGFlciJ9.V5exVQ92sZRwRxKeOFxqb4DzWaMTnKu-VmhW-r1pg8E'
a11n_h, a11n_d, a11n_s = access_token.split('.')
response = redirect('http://localhost/about', 302)
response.set_cookie('a11n.h', a11n_h, secure=True)
response.set_cookie('a11n.d', a11n_d, secure=True)
response.set_cookie('a11n.s', a11n_s, secure=True, httponly=True)
return response
You would have 3 cookies:
a11n.h: the header (options: Secure)
a11n.d: the payload (options: Secure)
a11n.s: the signature (options: Secure, Http-Only)
The consequence is:
a11n.d cookie is accessible from your React app (you can even get userinfo from it)
a11n.s cookie is not accessible from Javascript
You have to reassemble the access_token from cookies on server-side before sending request to Spotify
To reassemble the access_token:
#app.route('/resource')
def resource():
a11n_h = request.cookies.get('a11n.h')
a11n_d = request.cookies.get('a11n.d')
a11n_s = request.cookies.get('a11n.s')
access_token = a11n_h + '.' + a11n_d + '.' + a11n_s
jwt.decode(access_token, verify=True)
I hope it helps!
Disclaimer:
Code samples need to be improved (error handling, checks, etc). They are only examples to illustrate the flow.
I have a ReactJS app running in browser, which needs access to my backend laravel-passport API server. So, I am in control of all code on both client and server side, and can change it as I please.
In my react app, the user logs in with their username and password, and if this is successful, the app recieves a personal access token which grants access to the users data. If I store this token in local storage, the app can now access this users data by appending the token to outgoing requests.
But I do not want to save the access token in local storage, since this is not secure. How do I do this?
Here is what I have tried:
In the laravel passport documentation, there is a guide on how to automatically store the access token in a cookie. I believe this requires the app to be on the same origin, but I cannot get this to work. When testing locally, I run the app on localhost:4000, but the API is run on my-app.localhost. Could this be a reason why laravel passport does not make a cookie with the token, although they technically both have origin localhost?
OAuth has a page on where to store tokens. I tried the three options for "If backend is present", but they seem to focus on how the authorization flow rather than how to specifically store the token.
Here's the relevant parts of my code (of course, feel free to ask for more if needed):
From my react app:
const tokenData = await axios.post(this.props.backendUrl + '/api/loginToken', { email: 'myEmail', password: 'myPassword' })
console.log('token data: ', tokenData)
const personalAccessToken = tokenData.data.success.token;
var config = {
headers: {
'Authorization': "Bearer " + personalAccessToken
};
const user = await axios.get(this.props.backendUrl + '/api/user', config);
From the controller class ApiController:
public function loginToken()
{
if (Auth::attempt(['email' => request('email'), 'password' => request('password')])) {
$user = Auth::user();
$success['token'] = $user->createToken('MyApp')->accessToken;
return response()->json(['success' => $success], 200);
} else {
return response()->json(['error' => 'Unauthorised'], 401);
}
}
and the loginToken function is called from the /api/loginToken route.
Expected and actual results:
Ideally, I would love to have the token saved in a cookie like in the passport documentation, so I don't even have to attach the token to outgoing requests from the react app, but I'm not sure that this is even possible. Perhaps with third party cookies?
Else, I'd just like to find some way to store the token securely (for example in a cookie?), and then append it to outgoing calls from the react app.
In my project , the Contact Request Form is sent to an HTTP Firebase function ( Google Cloud Function wrapper...) the received data should be sent to a gmail user contact#mydomain.org (G Suite account)
( I am trying NOT to use another service like SendGrid... )
Currently I send the message with gmail.users.messages.send() , obviously GMail API is overwriting the From: original sender email with Googlea account admin email...
I tried to run before a gmail.users.settings.sendAs.create() to add the original from: email address, but in this case I need also to setup an smtpMsa server to be used as a relay ...
Is there anyway to BUILD a mail message from the data received ( from: msg_text: ) and INSERT IT DIRECTLY into my contact#mydomain.org gmail box to 'simulate' a received message with the correct information ? or should it be sent only by an smtp server ?
UPDATE
I can use users.message.import() ...
but I get an error when if I import into another email address ( contact# ) than the account admin ...
{"infos":"Delegation denied for admin#mydomain.org"}
THIS WORKS
return gmail.users.messages.import({
userId: config_key.admin_email,
resource: {
raw: encodedMessage
}
});
THIS DON't WORK
return gmail.users.messages.import({
userId: config_key.contact_email,
resource: {
raw: encodedMessage
}
});
YES WE CAN !
I succeeded finally ... inserting directly a message into my Google Suite email address .. and the from: address is actually the original contact form sender email address !
function gMailInserMessage (sender_name, sender_email, msg_text) {
// Create a new JWT client using the key file downloaded from the Google Developer Console
const jwtClient = new google.auth.JWT(
postoffice_key.client_email, // service account with Domain-Wide delagation of Authority
null,
postoffice_key.private_key,
_.values(config_key.scopes.postoffice),
config_key.admin_email // subject (or sub) impersonated user
);
return jwtClient.authorize().then(tokens => {
// Obtain a new gmail client, making sure we pass along the auth client
const gmail = google.gmail({
version: 'v1',
auth: jwtClient
});
// Base64-encode the mail and make it URL-safe
// (replace all "+" with "-" and all "/" with "_")
var encodedMessage = btoa([
'From: ' + sender_email + '\r\n',
'To: ' + config_key.admin_email + '\r\n',
'Subject: Contact\r\n\r\n',
msg_text
].join("")).replace(/\+/g, '-').replace(/\//g, '_');
//Make an authorized request to insert a User Messages
return gmail.users.messages.insert({
userId: 'me', // impersonated authorized user
resource: {
raw: encodedMessage
}
});
}).then(res => res.data);
}
the response is :
{"status":200,"infos":
{"id":"1639b41c26584963","threadId":"1639b41c26584963"}}