End Point API, FastAPI and supabase, sign_in & sign_out user - session-cookies

I'm facing a problem with my backend app, I want to create 3 endpoint API:
/login user
/logout
/getuser
I'm using python FastAPI and supabase, the question is how to create session from my backend app to get connected user information to handle th log_out and the log_in.
log_in function endpoint
#app.post('/login')
async def handel_login(user_mail: str):
if check(user_mail):
print(supabase.auth.sign_in(email=user_mail))
else:
print("incorrect form of email")
log_ou endpoint
#app.get('/logout')
async def handel_logout():
error = supabase.auth.sign_out()
return error
getuser endpoint
#app.get('/getuser')
async def get_user():
user = supabase.auth.user()
return user
in all of this I get None responses!

Supabase-py maintainer here - thanks for the query! FastAPI uses Starlet under the hood so your probably want to make use of the Session Middleware provided by Starlet.
Thereafter, you can do something similar of this format. This is for Django so you may need to make minor tweaks:
try:
data = supabase.auth.sign_in(email="generictestemail123now#gmail.com", password="supersecurepassword")
request.session['user'] = data.json()
except APIError:
pass
Thereafter, you can check if the user is authenticated by reading from the session and validating the JWT.
Hope this helps and please let me know if there are any further issues.
Additional References
[0] - session object in Fastapi similar to flask

Related

Allauth user_logged_in signal does not trigger in a TestCase

I'm using allauth for user management in my Django web app and I have functionality which finds session data stored when the user was not logged in and stores it when they next log in on the same session.
It is triggered on login via the standard allauth signal:
#receiver(user_logged_in)
def update_on_login(request, user, **kwargs):
print('Signal triggered')
# Do some stuff with session data here, e.g.:
for session_key in request.session.items():
...
This works perfectly when logging in via a web browser, but in TestCase functions the signal is not triggered at all when logging in with:
self.client = Client()
logged_in = self.client.login(username=self.username, password=self.password)
(I've also tried force_login and force_authenticate, but info on these indicates that they actually skip all real validation and just assume the user is logged in).
I think I've understood that client.login doesn't work because the test client doesn't really handle the request in the same way as a web browser would, but I can't say I really understand it.
I've also seen some indication that RequestFactory might be able to help in someway, but I haven't been able to trigger the signal with this either:
request = RequestFactory().post(reverse('account_login'), { 'username': self.username, 'password': self.password })
request.user = AnonymousUser()
response = login(request)
I've seen some comments about needing to call middleware as well, but pretty out of date and nothing clear enough for me to understand and try.
Any suggestions to point me in the right direction would be much appreciated.
Thanks in advance.

Stoping saved request that triggered the login, always redirect to `/`

I have a web application that contains a REST api that is interacted with by the client application that lives at /. When a user session times out and the client js application makes a request to the REST api, that request will trigger a login event. Once the user logs in the user is then taken to the REST api endpoint which just displays a JSON response. I would like to hard wire the login page to always redirect to /.
Edit 1: I'm using spring-security-core plugin with the openid plugin as well and grails 2.0.4.
Edit 2: So I managed to get a solution working, however its a bit crude and I would like to know if there is a more elegant solution out there.
I created a simple filter in grails-app/conf/LoginRedirectFilters.groovy:
class RedirectLoginFilters {
def filters = {
redirectAfterLogin (uri: '/api/*') {
before = {
if (session["LOGGING_IN"])
redirect uri: "/"
}
after = {
if (session["LOGGING_IN"])
session["LOGGING_IN"] = false
}
}
}
}
And in my auth function inside of OpenIdController.groovy I added the session flag LOGGING_IN:
def auth = {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
redirect uri: config.successHandler.defaultTargetUrl
return
}
session["LOGGING_IN"] = true
.
.
.
This is working properly by only checking if the LOGGING_IN flag is true when an api endpoint is called, and it also kills the flag after one request so it won't interfere with subsequent client api calls. I realize this is pretty convoluted, so if there is a better solution please let me know, thanks!
Why can't you use the same "springSecurityService.isLoggedIn()" inside your filter as well? I'm pretty sure that works as well.
I think I'm misunderstanding exactly what you want to achieve, but if you always want to redirect to '/' after a successful login, just set these properties in your Config.groovy:
grails.plugins.springsecurity.successHandler.alwaysUseDefault = true
grails.plugins.springsecurity.successHandler.defaultTargetUrl = "/"
If you want different behaviour to this, you'll have to flesh out your question.

Linkedin Oauth with Rails

We have been connecting to Linkedin for awhile now successfully. However, we get some errors from time to time and I'm hoping someone can help shed some light on this. Here's our code:
def linkedin_login
request_token = Linkedin.client.request_token(oauth_callback: "http://#{SITE_URL}/linkedin/auth/")
session[:linkedin_request_token] = request_token.token
session[:linkedin_request_secret] = request_token.secret
redirect_to request_token.authorize_url
end
def linkedin_auth
raise "Don't have proper session or oauth_verifier" if session[:linkedin_request_token].blank? or session[:linkedin_request_secret].blank? or params[:oauth_verifier].blank?
access_token = Linkedin.client.authorize_from_request(session[:linkedin_request_token], session[:linkedin_request_secret], params[:oauth_verifier])
raise "Nil access token" if access_token.blank?
redirect_to linkedin_process_path(token: access_token.first, secret: access_token.second)
end
We're hitting the "raise 'Don't have proper session or oauth_verifier'" more than I would expect. When looking at the ENV for the errors, those people don't have the session values set from the original method. We have before_filters set on the application controller so initialize the session, so I know it's active.
My next thought was whether "request_token" was generating a value request_token, and I've tried many times and they all bring something back. We get many of these a day. After the error, if the user tries again, it works fine, which is why I'm so confused.
any thoughts on what could cause this?
Based on your code, it looks like you're making the request token call every time the user logs into your application. That's not the proper method to authenticate. You really only need to fetch the request token once, then use that to upgrade for an access token (as you're doing in your linkedin_auth method). From there, just save the access token and secret in your DB and fetch it anytime you need to make an API call for that particular user.
Our authentication is described more in detail here: https://developer.linkedin.com/documents/authentication
Also, this is just a personal preference, but I like using the OAuth gem for Rails as opposed to using a LinkedIn wrapper. It's easy to use and light weight.
Just as an example, you could do your auth this way:
require 'oauth'
def auth
api_key = 'XXXXXXXXX'
api_secret = 'XXXXXXXXX'
configuration = { :site => 'https://api.linkedin.com',
:authorize_path => 'https://www.linkedin.com/uas/oauth/authenticate',
:request_token_path => 'https://api.linkedin.com/uas/oauth/requestToken',
:access_token_path => 'https://api.linkedin.com/uas/oauth/accessToken' }
consumer = OAuth::Consumer.new(api_key, api_secret, configuration)
#Request token
request_token = consumer.get_request_token
# Output request URL to console
puts "Please visit this URL: https://api.linkedin.com/uas/oauth/authenticate?oauth_token=" + request_token.token + " in your browser and then input the numerical code you are provided here: "
# Set verifier code
verifier = $stdin.gets.strip
# Retrieve access token object
#access_token = request_token.get_access_token(:oauth_verifier => verifier)
end
You would only need to invoke this method when the user first authorizes your app. Save their access token then use it for subsequent API calls. Note, my example makes use of the console to enter the PIN verifier. In a real world example you'd want to programmatically save the PIN in a session variable or in memory, then use it to get the access token.

Tumblr OAuth Callback URL

I'm setting the oauth_callback URL in the request header, when I do this in Twitter, it works fine and the user is redirected to the callback URL. But using Tumblr's API, the callback URL is ignored and the user is redirected to the default URL. Has anybody else experienced this? Is there anywhere else other than the header that I should be setting this? I tried passing it in as a parameter but that didn't really work either.
Any help would be appreciated.
According to Tumblr's developer blog, this was a bug in Tumblr's API and has been fixed.
Many of you have been dismayed that you could not override the
callback url when a user was attempting to authorize their
application. Good news: we’ve patched the bug that was causing this
particular issue.
Now, you can pass a url with the oauth_callback parameter and we will
redirect the user to that endpoint once you’re done.
Let’s go over a quick example.
When the user is presented with the screen to authorize your app, you
should be able to override your default callback with the
oauth_callback parameter in your url.
http://www.tumblr.com/oauth/authorize?oauth_token=your_token&oauth_callback=http%3A%2F%2Fmysite.com/oauth_callback/testing
The above url will redirect the user to
mysite.com/oauth_callback/testing and let you know if the user has
approved or denied your app.
Update March 14, 2013:
Starting today, Tumblr is no longer respecting the oauth_callback parameter. The blog post that I previously linked to has been deleted. I ended up using a variation of the accepted answer to work around it.
If you are trying a embed userid in callback url then this post can help you.
You can save your oauth token in a session and later on callback you can retrieve user from session.
on token request:
def ask_access
tumblr_consumer = get_consumer
if tumblr_consumer
#1. get a request token
request_token = tumblr_consumer.get_request_token
session[:request_token] = request_token
session[:user_token] = "#{request_token.params[:oauth_token]}_#{current_user.id}"
#2. have the user authorize
redirect_to request_token.authorize_url
else
render :text=> "Failed to acquire request token from Tumblr."
end
end
on call back:
def call_back
if params[:oauth_token] && params[:oauth_verifier]
request_token = session[:request_token]
user_id = session[:user_token].split("_")[1]
user = UserProfile.find user_id
##3. get an access token
access_token = request_token.get_access_token({:oauth_verifier => params[:oauth_verifier]})
user.tumblr_token = access_token.params[:oauth_token]
user.tumblr_secret = access_token.params[:oauth_token_secret]
user.save!
end
end
Tumblr does this (I assume) for security. They require that the callback URL is defined on application registration and they will not let it be overridden during implementation.
The security issue is to make sure that no one can steal your Application Token and try to use it to use your reputation to get access to customer's data. By forcing all callbacks to go to the default URL, they can guarantee that only your application is able get the Access Tokens.
The two ways to handle this are:
1) Have the default URL do a redirect to where you want it to go based on cookie or some other data
2) Have different application tokens for different callback URLs.
I can't respond to Jonathan Tran's answer, since my account is young, but posting the callback URL in the authorization URL no longer works, as he says. I asked on Twitter, and here was John Bunting's response:
https://twitter.com/codingjester/status/313248230987157505
I successfully was able to reroute my callback URL using the following (here in Python), after assigning all the proper keys:
consumer = oauth.Consumer(consumer_key, consumer_secret)
client = oauth.Client(consumer)
resp, content = client.request(request_token_url, "GET")
resp, content = client.request(request_token_url, "POST", body=urllib.urlencode({"oauth_callback": "[your own URL here]"}))
Tumblr implements this behavior differently from Twitter, so the same use of the Ruby OAuth library yields different results.
For your value of #callback_url, this works in Twitter:
#request_token = #oauth.get_request_token({
oauth_callback:#callback_url
})
redirect_to #request_token.authorize_url
But for Tumblr, you will be redirected to your default URL. To specify a different URL, you should do this:
#request_token = #oauth.get_request_token
redirect_to #request_token.authorize_url + '&' + { oauth_callback:#callback_url }.to_query
This is consistent with their documentation/blog post (cited in another answer). I have not checked to see if this is "correct" according to the OAuth 1.0a specification.

Google Federated OAuth/OpenID with Tornado: why is it ignoring my scopes?

I'm trying to use Tornado's library for federated login to authenticate users and get access to their calendar, contacts, and mail. However, when I get the "mydomain.dyndns.info is asking for some information from your Google Account" message, the only bullet point listed is "Email Address". Subsequently, when I check the returned user object after I approve the request, the user object doesn't have an 'access_token' property.
Here's the code:
def get(self):
scope_list = ['https://mail.google.com/','http://www.google.com/m8/feeds/','http://www.google.com/calendar/feeds/']
...
self.authorize_redirect(scope_list, callback_uri=self._switch_command('auth_callback'), ax_attrs=["name","email"])
def _on_auth(self, user):
print 'in on auth'
if user:
self.set_the_user(user['email'])
session.set_data('usertoken_' + user['email'], user['access_token'])
self.redirect('/')
The uri that this spits out is:
https://www.google.com/accounts/o8/ud
?openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0
&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select
&openid.return_to=http%3A%2F%2Fmydomain.dyndns.info%3A333%2Fauth%2Fauth_callback%3Fperms%3Dgmail%26perms%3Dcontacts%26perms%3Dcalendar
&openid.realm=http%3A%2F%2Fmydomain.dyndns.info%3A333%2F
&openid.mode=checkid_setup
&openid.ns.oauth=http%3A%2F%2Fspecs.openid.net%2Fextensions%2Foauth%2F1.0
&openid.oauth.consumer=mydomain.dyndns.info
&openid.oauth.scope=https%3A%2F%2Fmail.google.com%2F+http%3A%2F%2Fwww.google.com%2Fm8%2Ffeeds%2F+http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F
&openid.ns.ax=http%3A%2F%2Fopenid.net%2Fsrv%2Fax%2F1.0
&openid.ax.type.fullname=http%3A%2F%2Faxschema.org%2FnamePerson
&openid.ax.type.lastname=http%3A%2F%2Faxschema.org%2FnamePerson%2Flast
&openid.ax.type.firstname=http%3A%2F%2Faxschema.org%2FnamePerson%2Ffirst
&openid.ax.mode=fetch_request
&openid.ax.type.email=http%3A%2F%2Faxschema.org%2Fcontact%2Femail
&openid.ax.required=firstname%2Cfullname%2Clastname%2Cemail
Ideas: 1. maybe this has something to do with the fact I'm running on a local machine behind a dyndns forwarder? 2. Tornado's documentation says "No application registration is necessary to use Google for authentication or to access Google resources on behalf of a user" -- but maybe that's not true anymore?
If anyone has thoughts, I'd really appreciate it -- this is driving me a little batty!
Figured it out. You have to set the application properties google_consumer_key and google_consumer_secret.
application = tornado.web.Application(urlhandlers, cookie_secret=cookie_secret, google_consumer_key=google_consumer_key, google_consumer_secret=google_consumer_secret)
You get them by going here: https://www.google.com/accounts/ManageDomains

Resources