Django OAuth custom Authentication class - oauth

Need to override the current authentication view api/v1/o/token to add my custom error messages based on username and password.
1.
{
"status = ok // need to add this
"access_token": "xxxx",
"token_type": "Bearer",
"expires_in": 60,
"refresh_token": "xxxxaaaxxxx",
"scope": "read write"
}
2.
status = 'not_active'
detail= 'user not activated'
3.
status = 'error'
detail= 'Incorrect username or password'
I want to disable the application create on my production hosting.
How can I do that.?

This is how you create a custom authentication class with Django Rest Framework. Subclass BaseAuthentication and override the .authenticate(self, request) method.
from django.contrib.auth.models import User
from rest_framework import authentication
from rest_framework import exceptions
class CustomAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
"""
Consider the method validate_access_token() takes an access token,
verify it and return the User.username if the token is valid else None
"""
username = validate_access_token(request.META.get('X_ACCESS_TOKEN'))
if not username:
return None #return None if User is not authenticated.
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
then change DEFAULT_AUTHENTICATION_CLASSES in settings to point to the custom authentication class
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'api.core.auth.CustomAuthentication',
),
}

Related

Apache Superset and Auth0 returns "The browser (or proxy) sent a request that this server could not understand."

I'm trying to set up Superset with Auth0. I've found somewhat similar issues here and here.
I've set up the following configuration based on the first link above and trying to follow the Superset and Flask-AppBuilder docs:
from flask_appbuilder.security.manager import (
AUTH_OAUTH,
)
from superset.security import SupersetSecurityManager
import json
import logging
import string
import random
nonce = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k = 30))
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Admin"
AUTH0_URL = os.getenv('AUTH0_URL')
AUTH0_CLIENT_KEY = os.getenv('AUTH0_CLIENT_KEY')
AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
OAUTH_PROVIDERS = [
{ 'name':'auth0',
'token_key':'access_token',
'icon':'fa-at',
'remote_app': {
'api_base_url': AUTH0_URL,
'client_id': AUTH0_CLIENT_KEY,
'client_secret': AUTH0_CLIENT_SECRET,
'server_metadata_url': AUTH0_URL + '/.well-known/openid-configuration',
'client_kwargs': {
'scope': 'openid profile email'
},
'response_type': 'code token',
'nonce': nonce,
}
}
]
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
logger.debug('oauth2 provider: {0}'.format(provider))
if provider == 'auth0':
res = self.appbuilder.sm.oauth_remotes[provider].get(AUTH0_URL + '/userinfo')
logger.debug('response: {0}'.format(res))
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.json())
return
# user_info = self.appbuilder.sm.oauth_remotes[provider].parse_id_token(res)
# logger.debug('user_info: {0}'.format(user_info))
me = res.json()
return {
'username' : me['email'],
'name' : me['name'],
'email' : me['email'],
}
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
The full error log message is:
2022-03-18 18:53:56,854:ERROR:flask_appbuilder.security.views:Error authorizing OAuth access token: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
NOTES:
I can see an access_token parameter in the redirect url, so it seems to be working with Auth0 correctly.
I don't see any of the debug lines in the CustomSsoSecurityManager being written, so my guess is that I have not correctly set that up (or my logging is not correctly configured).
I've tried using both Regular Web Application and Single Page Application application types in Auth0, and both fail in the same way.
I would appreciate any help in understanding what I might be missing or what else I need to do to configure Auth0 to work with Superset.
I was able to make it work using the JSON Web Key Set endpoint provided by Auth0, look at this example and adapt it accordingly:
from jose import jwt
from requests import request
from superset.security import SupersetSecurityManager
class CustomSecurityManager(SupersetSecurityManager):
def request(self, url, method="GET", *args, **kwargs):
kwargs.setdefault("headers", {})
response = request(method, url, *args, **kwargs)
response.raise_for_status()
return response
def get_jwks(self, url, *args, **kwargs):
return self.request(url, *args, **kwargs).json()
def get_oauth_user_info(self, provider, response=None):
if provider == "auth0":
id_token = response["id_token"]
metadata = self.appbuilder.sm.oauth_remotes[provider].server_metadata
jwks = self.get_jwks(metadata["jwks_uri"])
audience = self.appbuilder.sm.oauth_remotes[provider].client_id
payload = jwt.decode(
id_token,
jwks,
algorithms=["RS256"],
audience=audience,
issuer=metadata["issuer"],
)
first_name, last_name = payload["name"].split(" ", 1)
return {
"email": payload["email"],
"username": payload["email"],
"first_name": first_name,
"last_name": last_name,
}
return super().get_oauth_user_info(provider, response)

Apache Superset and Auth0 returns "unexpected keyword argument 'scope'"

Some time ago I've successfully integrated Superset authentication with Oauth using AWS Cognito.
Now I'm trying to do the same with Auth0, reusing the previous configuration and changing the endpoints according to Auth0 documentation.
Unfortunately, the login fails and Superset's log returns the following message:
2021-10-20 10:30:48,886:ERROR:flask_appbuilder.security.views:Error on OAuth authorize: request() got an unexpected keyword argument 'scope'
This is the Oauth configuration in superset_config.py:
from superset.security import SupersetSecurityManager
import json
import logging
logger = logging.getLogger(__name__)
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
if provider == 'auth0':
res = self.appbuilder.sm.oauth_remotes[provider].get('userinfo')
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.data)
return
me = json.loads(res._content)
logger.warning(" user_data: %s", me)
prefix = 'Superset'
logging.warning("user_data: {0}".format(me))
return {
'username' : me['email'],
'name' : me['name'],
'email' : me['email'],
'first_name': me['given_name'],
'last_name': me['family_name'],
}
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH0_URL = os.getenv('AUTH0_URL')
AUTH0_CLIENT_KEY = os.getenv('AUTH0_CLIENT_KEY')
AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
OAUTH_PROVIDERS = [{
'name':'auth0',
'token_key': 'access_token',
'icon':'fa-at',
'url': AUTH0_URL,
'remote_app': {
'client_id': AUTH0_CLIENT_KEY,
'client_secret': AUTH0_CLIENT_SECRET,
'request_token_params': {
'scope': 'email openid profile'
},
'response_type': 'token_id',
'base_url': AUTH0_URL,
'access_token_url': os.path.join(AUTH0_URL, 'oauth/token'),
'authorize_url': os.path.join(AUTH0_URL, 'authorize'),
'access_token_method':'POST',
'request_token_url': os.path.join(AUTH0_URL, 'oauth/token'),
'api_base_url': AUTH0_URL,
}
}
]
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
I have already tried different values for the response_type (code, token, token_id).
Also tried to leave request_token_url empty and in that case the error changes because the user data appear to be an empty dictionary:
2021-10-13 15:52:10,358:WARNING:superset_config: user_data: {}
2021-10-13 15:52:10,358:WARNING:root:user_data: {}
2021-10-13 15:52:10,358:ERROR:flask_appbuilder.security.views:Error returning OAuth user info: 'email'
So I assume the token is actually returned and I cannot understand why Flask is complaining about the attribute "scope".
Tried this too, since it looked like very similar to my problem, but none of those configurations work for me.
Hope you have two files as custom_sso_security_manager.py and superset_config.py
Can you remove below two line from return and try(custom_sso_security_manager.py).
'first_name': me['given_name'],
'last_name': me['family_name'],
This is for future reference, although I accepted Kamal's answer.
It turned out that the right parameter to set the request token scopes was client_kwargs instead of request_token_params.
This is a working configuration to authenticate Superset against Auth0:
## Enable OAuth authentication
from flask_appbuilder.security.manager import (
AUTH_OAUTH,
)
from superset.security import SupersetSecurityManager
import json
import logging
import string
import random
nonce = ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k = 30))
logger = logging.getLogger(__name__)
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
if provider == 'auth0':
res = self.appbuilder.sm.oauth_remotes[provider].get('userinfo')
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.json())
return
me = res.json()
return {
'username' : me['email'],
'name' : me['name'],
'email' : me['email'],
}
AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH0_URL = os.getenv('AUTH0_URL')
AUTH0_CLIENT_KEY = os.getenv('AUTH0_CLIENT_KEY')
AUTH0_CLIENT_SECRET = os.getenv('AUTH0_CLIENT_SECRET')
OAUTH_PROVIDERS = [
{ 'name':'auth0',
'token_key':'access_token',
'icon':'fa-at',
'remote_app': {
'api_base_url': AUTH0_URL,
'client_id': AUTH0_CLIENT_KEY,
'client_secret': AUTH0_CLIENT_SECRET,
'server_metadata_url': os.path.join(AUTH0_URL, '.well-known/openid-configuration'),
'client_kwargs': {
'scope': 'openid profile email'
},
'response_type': 'code token',
'nonce': nonce,
}
}
]
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
As per Flask Documentation,
try to use client_kwargs instead of request_token_params key.
Sample:
{
'name':'google',
'icon':'fa-google',
'token_key':'access_token',
'remote_app': {
'client_id':'GOOGLE_KEY',
'client_secret':'GOOGLE_SECRET',
'api_base_url':'https://www.googleapis.com/oauth2/v2/',
'client_kwargs':{
'scope': 'email profile'
},
'request_token_url':None,
'access_token_url':'https://accounts.google.com/o/oauth2/token',
'authorize_url':'https://accounts.google.com/o/oauth2/auth'
}
},

Add custom field to Django Rest Framework generation token

With Django Rest Framework i try to add a custom field (clan) when i want to generate token, it should be a required field and an existing clan.
Like :
curl -X POST -d "grant_type=password&username=username&password=password&client_id=client_id&client_secret=client_secret&clan=ABCDEF" http://localhost:8000/o/token/
My User model :
class User(AbstractUser):
"""
User model.
"""
clans = models.ManyToManyField(Clan, related_name='Users')
My rest framework settings:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apps.core.api.pagination.StandardPagination',
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAdminUser',
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'EXCEPTION_HANDLER': 'apps.core.exceptions.api.custom_exception_handler',
}
Then i want a classic response:
{
"access_token": "azerty123456789",
"expires_in": 36000,
"token_type": "Bearer",
"scope": "read write groups",
"refresh_token": "azerty123456789"
}
Finally i found the solution :
You need to override the class TokenView from oauth2_provider.views.base then override the post method.
class MyCustomToken(TokenView):
"""
....
"""
#method_decorator(sensitive_post_parameters("password"))
def post(self, request, *args, **kwargs):
"""
...
"""
# Check if the clan parameter is given in parameter.
clan_parameter = request.POST.get('clan', False)
if not clan_parameter:
return JsonResponse(
{'clan': 'Parameter is required.'},
status = 401
)
url, headers, body, status = self.create_token_response(request)
if status == 200:
access_token = json.loads(body).get("access_token")
if access_token is not None:
token = get_access_token_model().objects.get(
token=access_token)
app_authorized.send(
sender=self, request=request,
token=token)
response = HttpResponse(content=body, status=status)
for k, v in headers.items():
response[k] = v
return response

Using facebook social plugin how to get email address in grails

I want to get email address using social plugin of grails . I am using facebook plugin compile ':spring-security-oauth-facebook:0.1' and configured properly in Config.groovy like below.
oauth {
debug = true
providers {
facebook {
api = org.scribe.builder.api.FacebookApi
key = 'here is my-key'
secret = 'my-secret-key'
successUri = '/oauth/facebook/success'
failureUri = '/oauth/facebook/failure'
callback = "${baseURL}/oauth/facebook/callback"
scopes = "email"
}
}
}
After get successfully response, below method is called.
def onSuccess(String provider) {
if (!provider) {
log.warn "The Spring Security OAuth callback URL must include the 'provider' URL
parameter"
throw new OAuthLoginException("The Spring Security OAuth callback URL must include the
'provider' URL parameter")
}
def sessionKey = oauthService.findSessionKeyForAccessToken(provider)
if (!session[sessionKey]) {
log.warn "No OAuth token in the session for provider '${provider}'"
throw new OAuthLoginException("Authentication error for provider '${provider}'")
}
}
OAuthToken oAuthToken = springSecurityOAuthService.createAuthToken(provider,
session[sessionKey])
println "oAuthToken.principal = "+oAuthToken.principal.toString();
println "oAuthToken.socialId = "+oAuthToken.socialId;
println "oAuthToken.properties= "+oAuthToken.properties
println "oAuthToken.properties= "+oAuthToken.name
println "oAuthToken.properties= "+oAuthToken.toString();
How to get email address by using this. I successfully get response from facebook but it is same number for Username,socialId,profileId but i need email Address like myemailid#gmail.com
please help me.

oauth facebook with grails accessing token

Its a grails project,
Facebook authentication is successful via oauth,
Now when it comes back to my controller, I want to get emailID of the logged in user,
Searched a lot, but did not find proper documentation,
I am using scribe and have following code in Config.groory
import org.scribe.builder.api.FacebookApi
oauth {
providers {
facebook {
api = FacebookApi
key = 'xxxx'
secret = 'yyyy'
callback = "http://my-domain-name-here:8080/TestOAuth2/dashBoard/facebooklogin"
successUri = "http://my-domain-name-here:8080/TestOAuth2/dashBoard/success"
}
}
}
Any help much appreciated.
Thanks.
Try this..,.
Config:
import org.scribe.builder.api.FacebookApi
...
oauth {
providers {
facebook {
api = FacebookApi
key = 'XXX'
secret = 'YYY'
scope = 'email,read_stream,publish_actions,user_birthday,publish_stream'
callback = "http://localhost:8080/appName/oauth/facebook/callback" //callback to oauth controller of oauth plugin
successUri = "http://localhost:8080/appName/myController/facebookSuccess"
failureUri = "http://localhost:8080/appName/myController/facebookFailure"
}
}
}
MyController:
def facebookSuccess() {
Token facebookAccessToken = (Token) session[oauthService.findSessionKeyForAccessToken('facebook')]
def facebookResource = oauthService.getFacebookResource(facebookAccessToken, "https://graph.facebook.com/me")
def facebookResponse = JSON.parse(facebookResource?.getBody())
log.info "Email = ${facebookResponse.email}"
...
}
You can get working example from my git repo. Grails Oauth Plugin Demo.
Email is not part of a Facebook public_profile. The only way to get the users e-mail address is to request extended permissions on the email field. You can do this by adding a scope to the oauth provider.
config.groovy
oauth {
providers {
facebook {
api = org.scribe.builder.api.FacebookApi
scope = 'email'
...
...
}
}
}
As an example of how to return email and various public_profile fields please see below.
Take Note of: getFacebookResource params e.g. https://graph.facebook.com/me?fields=id,name,verified,age_range,email"
import grails.converters.JSON
import org.scribe.model.Token
import grails.plugin.springsecurity.oauth.OAuthToken
class SpringSecurityOAuthController {
def oauthService
def onSuccess = {
// Validate the 'provider' URL. Any errors here are either misconfiguration
// or web crawlers (or malicious users).
if (!params.provider) {
renderError 400, "The Spring Security OAuth callback URL must include the 'provider' URL parameter."
return
}
def sessionKey = oauthService.findSessionKeyForAccessToken(params.provider)
if (!session[sessionKey]) {
renderError 500, "No OAuth token in the session for provider '${params.provider}'!"
return
}
// Create the relevant authentication token and attempt to log in.
OAuthToken oAuthToken = createAuthToken(params.provider, session[sessionKey])
Token facebookAccessToken = (Token) session[oauthService.findSessionKeyForAccessToken('facebook')]
def facebookResource = oauthService.getFacebookResource(facebookAccessToken , "https://graph.facebook.com/me?fields=id,name,verified,age_range,email")
def facebookResponse = JSON.parse(facebookResource?.getBody())
println facebookResponse
...
...
}
}
public_profile (Default)
A person's public profile refers to the following properties on the user object by default:
id cover
name
first_name
last_name
age_range
link
gender
locale
picture
timezone
updated_time
verified

Resources