proper user creation flow with GetStream.io and firebase? - node.js

I am new to getStream.io and I am trying to understand a user creation flow with getstream.io and firebase. If I create a new user in firebase and then pass in their firebase UID to functions such as:
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET');
//generate new user
client.user('<FIREBASE UID>').create({name: "Jane Doe", occupation: "Software Engineer", gender: 'female'});
//generate token for the user
const userToken = client.createUserToken('<FIREBASE UID>');
//Allow user to follow a feed
timeline_feed_1.follow('user', '<FIREBASE UID>');
//Check followers for the user
<FIREBASE UID>.followers({limit: '10', offset: '10'});
Would this work or am I going about this all wrong?
Thank you for reading!
P.S I have looked at Users auth and profiles in getstream.io and just wanted to clarify that my firebase example is what was meant by "Stream is best used in combination with an application"

I implemented a Firebase + GetStream.io user creation flow and can share what I did.
Big picture: After creating a Firebase UID, you have to use your own backend server to connect with the Stream API to create a new user (use the Firebase UID as the user_id) and generate that user's JSON Web Token ("JWT"). Your backend server then passes this JWT to your front end client (Swift iOS in my case), which then uses this JWT to allow the user to connect to the Stream API and access his authorized feeds etc. I used Python runtime Google Cloud Functions with a HTTP trigger as my "backend server". My Swift code called these functions via an HTTP POST request.
Here is my Python code to create a Stream user, substitute your own API key and secret:
import stream
from flask import escape
def createStreamUser(request):
content_type = request.headers['content-type']
if content_type == 'application/json':
request_json = request.get_json(silent=True)
try:
id = request_json['id']
name = request_json['data']['name']
avatarURL = request_json['data']['avatarURL']
except:
raise ValueError("JSON is invalid, or missing a 'name' property")
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', location='us-east')
userInfo = client.users.add(
id,
{"name": name},
get_or_create=True,
)
return
Here is a function which generates and returns a JWT to your front end client:
import stream
from flask import escape
def createUserToken(request):
content_type = request.headers['content-type']
if content_type == 'application/json':
request_json = request.get_json(silent=True)
try:
id = request_json['id']
name = request_json['data']['name']
except:
raise ValueError("JSON is invalid, or missing a 'name' property")
client = stream.connect('YOUR_API_KEY', 'API_KEY_SECRET', location='us-east')
user_token = client.create_user_token(id)
return(user_token)

It looks like Stream is best used in combination with an application in the answer you referenced was about using Stream API on a server and authenticate users there and then provide your frontend code with a user token after successful authentication.
Stream API client initialised using user tokens has restricted access in terms of which feeds are accessible or writable.
It is not recommended to put API secret in your frontend code as it may lead to unauthorised access to other users' data if someone extracts it from your app.

Related

Query on Microsoft Graph API Python

I want to pull emails by Graph API from client inbox using python.
I started with a tutorial and successfully experimented over my personal inbox.
My problem,
Every time my code generates an authorization URL.
I have to browse through it (using web browser library) , sign in using my credentials and copy paste the authorization code for generating access token.
Which is a lot of manual work every time.
Question :
Is there a way to automate the whole process of token generation ?
Such that my client only shares his application id and client secret, and email is pulled without his sign in credentials ?
My code is attached below -
import msal
from msal import PublicClientApplication
import webbrowser
import requests
import pandas as pd
APPLICATION_ID="app id"
CLIENT_SECRET="client secret"
authority_url='https://login.microsoftonline.com/common/'
base_url = 'https://graph.microsoft.com/v1.0/'
endpoint_url = base_url+'me'
SCOPES = ['Mail.Read','Mail.ReadBasic']
client_instance = msal.ConfidentialClientApplication(client_id = APPLICATION_ID,client_credential = CLIENT_SECRET,authority = authority_url)
authorization_request_url=client_instance.get_authorization_request_url(SCOPES)
#print(authorization_request_url)
# browsing authorization request URL for retrieving authorization code.
webbrowser.open(authorization_request_url,new=True)
# Manually pasting authorization code.
authorization_code='authorization code from authorization URL'
access_token = client_instance.acquire_token_by_authorization_code(code=authorization_code,scopes=SCOPES)
access_token_id=access_token['access_token']
# Rest of the codes are for hitting the end point and retrieving the messages
Any help with code suggestions will be much appreciated.
Thanks in advance
If you would like to authenticate only with a clientId and clientSecret, without any user context, you should leverage a client credentials flow.
You can check this official MS sample that uses the same MSAL library to handle the client credentials flow. It is quite straightforward, as you can see below:
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
import json
import logging
import requests
import msal
# Optional logging
# logging.basicConfig(level=logging.DEBUG)
config = json.load(open(sys.argv[1]))
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
# token_cache=... # Default cache is in memory only.
# You can learn how to use SerializableTokenCache from
# https://msal-python.rtfd.io/en/latest/#msal.SerializableTokenCache
)
# The pattern to acquire a token looks like this.
result = None
# Firstly, looks up a token from cache
# Since we are looking for token for the current app, NOT for an end user,
# notice we give account parameter as None.
result = app.acquire_token_silent(config["scope"], account=None)
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
result = app.acquire_token_for_client(scopes=config["scope"])
if "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']}, ).json()
print("Graph API call result: ")
print(json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
The sample is retrieving a list of users from MS Graph, but it should be just a matter of adapting it to retrieve the list of emails of a specific user by changing the "endpoint" parameter in the parameters.json file to:
"endpoint": "https://graph.microsoft.com/v1.0/users//users/{id | userPrincipalName}/messages"
You can check here more information regarding the MS Graph request to list emails.
register your app
get your tenant id from azure portal and disable mfa
application_id = "xxxxxxxxxx"
client_secret = "xxxxxxxxxxxxx"
#authority_url = "xxxxxxxxxxx"
authority_url = 'xxxxxxxxxxxxxxxxxxxx'
base_url = "https://graph.microsoft.com/v1.0/"
endpoint = base_url+"me"
scopes = ["User.Read"]
tenant_id = "xxxxxxxxxxxx"
token_url = 'https://login.microsoftonline.com/'+tenant_id+'/oauth2/token'
token_data = {
'grant_type': 'password',
'client_id': application_id,
'client_secret': client_secret,
'resource': 'https://graph.microsoft.com',
'scope':'https://graph.microsoft.com',
'username':'xxxxxxxxxxxxxxxx', # Account with no 2MFA
'password':'xxxxxxxxxxxxxxxx',
}
token_r = requests.post(token_url, data=token_data)
token = token_r.json().get('access_token')
print(token)

How to use Cognito for AppSync mutation call (Python)

I'd like to call mutations from AppSync using my Python function but use a Cognito user for the authorization as "API-KEY", "IAM" and other methods are not suitable for my application.
My mutation looks like this (test purposes):
mutation XYZ {
updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {
id
name
}
}
I am assuming that the user is already created and enabled by some means. If your AppSync API is secured only using Cognito, you are always going to need a username and a password to begin with. For example, you can use below code to login and get the AccessToken from the response:
import boto3
def get_user_auth(event, context):
client = boto3.client('cognito-idp')
response = client.initiate_auth(
UserPoolId='xxxxxxxxx',
ClientId='xxxxxxxxxxxxxx',
AuthFlow='USER_PASSWORD_AUTH',
AuthParameters={
'USERNAME': 'xxxxxx',
'PASSWORD': 'xxxxxx'
}
)
return response
Note: Make sure that you have "Enable username password based authentication (ALLOW_USER_PASSWORD_AUTH)" enabled.
Once you have the access token, you can use this in HTTP headers within your request as follows:
{
"authorization": "<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>"
}
For example:
import requests
from requests_aws4auth import AWS4Auth
import boto3
session = requests.Session()
APPSYNC_API_ENDPOINT_URL = '<YOUR-API-URL>'
mutation = """mutation XYZ {updateTask(input: {id: "a1b2c3", name: "newTaskName"}) {id, name}}"""
response = session.request(
url=APPSYNC_API_ENDPOINT_URL,
method='POST',
headers={'authorization': '<YOUR-VERY-VERY-LONG-ACCESS-TOKEN>'},
json={'mutation': mutation}
)
print(response.json()['data'])
Since this access token has some expiration, you might also need to refresh this token by using the RefreshToken from the above response. Like so:
def refresh_token(self, username, refresh_token):
try:
return client.initiate_auth(
ClientId=self.client_id,
AuthFlow='REFRESH_TOKEN_AUTH',
AuthParameters={
'REFRESH_TOKEN': refresh_token,
# 'SECRET_HASH': self.get_secret_hash(username)
# If the User Pool has been defined with App Client secret,
# you will have to generate secret hash as well.
}
)
except botocore.exceptions.ClientError as e:
return e.response
Example of how you can generate secret hash.

how to make the token active when the user is accessing the API

I have created a JWT Token based login system, I am able to generate the token and I have added the expiry time to that token.
Requiremenent:
When the user is accessing the UI the token should not expire.
When the User is not Accessing the UI for 10 minutes the token should expire.
I am using Angular for UI and python flask for backend, I don't no from where(either UI or Backend) I have to handle this. I am thinking we have to handle it from python flask so I have used python and flask tags, If I am wrong let me know.
my backend code:
def loginM(email, password):
try:
time_count = get_time_count_details()
user = Credentials.query.filter_by(email=email).first()
user_reg = Registration.query.filter_by(email=email).first()
if bcrypt.check_password_hash(user.password, password):
payload = {"email": user.email, 'user_id': user.user_id,
'first_name': user_reg.first_name,
'company': user_reg.company, 'mobile_num': user_reg.mobile_number,
'exp': time.time() + time_count}
secret_key = open(SECRET_KEY).read()
token = jwt.encode(payload, secret_key, algorithm='RS256').decode('utf-8')
return dict(token=token)
else:
return dict(Unsucessful="Invalid Email Address and password")
except Exception:
return False
you can use redis key expire instead of exp in jwt payload
jwt payload dont save exp value, jwt will not expired. payload like this:
payload = {"email": user.email, 'eco_user_id': user.eco_user_id,
'first_name': user_reg.first_name,
'company': user_reg.company, 'mobile_num': user_reg.mobile_number,}
redis save token,and set expiration as 10min
redis.set(token, user.id)
redis.expire(token, 60 * 10)
When the user is accessing the api, sever will find token in redis.if find token in redis,we will refresh redis expiration time,otherwise return 403 and tell user to login

JWT Authentication: Use UI token to authenticate Graphene/Django (GraphQL) queries?

I am working on a project with the following architecture:
UI: React on client and server-side rendering via a Node server, Apollo Client for GraphQL,
API: Django handles GraphQL queries through Graphene.
I use Auth0 (JWT based) for my frontend authentication. I would like to use the token I get to authenticate my user in the context of the GraphQL queries API side.
[Edit2]
To pass the token to my API, I use:
const idToken = cookie.load('idToken') || null;
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
req.options.headers.authorization = `Bearer ${idToken}`;
next();
}
}]);
Then I need to retrieve it in Django: I use django-jwt-auth and the code proposed by #Craig Ambrose.
My authorization header is received and decoded (I can get the payload) but there is a problem when verifying the signature: I get "Error decoding signature."
This is strange since the signature is verified when I test it on jwt.io.
How can I authenticate on Django side ?
I've just done this using django-jwt-auth (not using Auth0)
That package provides a JSONWebTokenAuthMixin that you can combine with the GraphQLView from graphene_django, for example.
from jwt_auth.mixins import JSONWebTokenAuthMixin
class AuthGraphQLView(JSONWebTokenAuthMixin, GraphQLView):
pass
urlpatterns = [
url(r'^graphql', csrf_exempt(AuthGraphQLView.as_view(schema=schema))),
url(r'^graphiql', include('django_graphiql.urls')),
]
This works, but I found that graphiql stopped working, because it wasn't sending to token. I wanted to keep using cookie based auth for that, for dev purposes, so changed it to the following.
from jwt_auth.mixins import JSONWebTokenAuthMixin
class OptionalJWTMixin(JSONWebTokenAuthMixin):
def dispatch(self, request, *args, **kwargs):
auth = get_authorization_header(request)
if auth:
return super(OptionalJWTMixin, self).dispatch(request, *args, **kwargs)
else:
return super(JSONWebTokenAuthMixin, self).dispatch(request, *args, **kwargs)
class AuthGraphQLView(OptionalJWTMixin, GraphQLView):
pass
urlpatterns = [
url(r'^graphql', csrf_exempt(AuthGraphQLView.as_view(schema=schema))),
url(r'^graphiql', include('django_graphiql.urls')),
]
My setting is working now:
I have used code from #Craig Ambrose with django-jwt-auth. I had to fork the package on Github to handle the Audience 'aud' payload present in Auth0 Token.
def jwt_get_user_id_from_payload_handler(payload):
sub = payload.get('sub')
Auth0User = import_string('project.models.Auth0User')
auth0_user = Auth0User.objects.filter(auth0_id=sub)[0]
user_id = auth0_user.user.id
return user_id
JWT_PAYLOAD_GET_USER_ID_HANDLER = jwt_get_user_id_from_payload_handler
auth0_key = '<MyAuth0SECRET>'
JWT_SECRET_KEY = base64.b64decode(auth0_key.replace("_","/").replace("-","+"))
JWT_VERIFY = True
JWT_AUTH_HEADER_PREFIX = 'Bearer'
JWT_AUDIENCE = '<MyAuth0CLIENT_ID>'
With Aut0User a model with OnoToOne relation with classic Django user and a field with auth0_id.

How to get saved tracks for a specific user, or change the current user?

The Spotify API has an endpoint "Get a Users's Saved Tracks" GET https://api.spotify.com/v1/me/tracks but as you can see from me in the url, and in the documentation, this is only for the current user. How can I access information about a non current user, or change the current user?
For example, userA logs in, I get an access and refresh token for userA. userB logs in, replacing userA as the current user, I get userB's tokens. How can I now make make requests for information about userA?
You need to store the tokens you get from authenticating users.
Say you're using user sessions:
User A logs in.
You get the access and refresh tokens for user A.
You save these tokens to User A's session.
User B logs in.
You get the access and refresh tokens for user B.
You save these tokens to User B's session.
You'd do this the same way that you have already implemented user sessions.
And so when a user lands on your redirect URI, you save the tokens you received to their session.
And then when you need to use the Spotify API you use the tokens saved in the users session.
If you however want to do this for one end-user, then with a web server things get a little harder.
But with a CLI app things can be a little easier.
What you want to do is log user A and B into your application, manually saving both tokens independently.
This is as easy as making an authentication function that you call twice and save the results to two variables.
After this you can then call the API with the saved tokens.
And use user A's token when you want to get user A's saved tracks.
Here's a low-level example implementation in Python 3 using Requests, of getting the users tracks and user information, using different scopes. Where the comments are the part the code's at in the authorization code flow:
import time
import urllib.parse as parse
import webbrowser
import requests
from requests.auth import HTTPBasicAuth
OAUTH_AUTHORIZE_URL = 'https://accounts.spotify.com/authorize'
OAUTH_TOKEN_URL = 'https://accounts.spotify.com/api/token'
# Change to your application settings
class Settings:
client_id = ''
client_secret = ''
redirect_uri = ''
def authenticate(scope=None):
'''Implement OAuth 2 Spotify authentication'''
# Application: Request authorization to access data
payload = {'client_id': Settings.client_id,
'response_type': 'code',
'redirect_uri': Settings.redirect_uri,
'show_dialog': 'true'} # allow second account to login
if scope:
payload['scope'] = scope
auth_url = '{}?{}'.format(OAUTH_AUTHORIZE_URL, parse.urlencode(payload))
# Spotify: Displays scopes & prompts user to login (if required)
# User: Logs in, authorizes access
webbrowser.open(auth_url)
response = input('Enter the URL you were redirected to: ')
code = parse.parse_qs(parse.urlparse(response).query)['code'][0]
payload = {'redirect_uri': Settings.redirect_uri,
'code': code,
'grant_type': 'authorization_code'}
if scope:
payload['scope'] = scope
# Application: Request access and refresh tokens
# Spotify: Returns access and refresh tokens
auth = HTTPBasicAuth(Settings.client_id, Settings.client_secret)
response = requests.post(OAUTH_TOKEN_URL, data=payload, auth=auth)
if response.status_code != 200:
response.raise_for_status()
token_info = response.json()
token_info['expires_at'] = int(time.time()) + token_info['expires_in']
token_info['scope'] = scope
return token_info
if __name__ == '__main__':
user_a = authenticate(scope='user-library-read')
user_b = authenticate(scope='user-read-email user-read-private user-read-birthdate')
print('user_a', user_a)
print('user_b', user_b)
for url in ['https://api.spotify.com/v1/me/tracks',
'https://api.spotify.com/v1/me']:
for user in [user_a, user_b]:
token = 'Bearer ' + user['access_token']
# Application: Uses access token in requests to Web API
# Spotify: Returns request data
r = requests.get(url, headers={'authorization': token})
if r.status_code != 200:
print(r.text)
else:
print([
'{}: {}'.format(key, str(value)[:20])
for key, value in r.json().items()
])

Resources