Keycloak API :- Identify Users Realm for login - keycloak-rest-api

I am creating a microservice that will proxy keycloak for user creation, reset password, login etc. I don't want to expose any keycloak page like re-set password or login page so I am using the keycloak API and everything is fine so far.
The only issue is for login, where I need to know the realm to get the token as the API to get the token is realm specific.
realms/{REALM_NAME}/protocol/openid-connect/token
So is there a way to get a list of all the users from all the realms by admin user?
or any other way to find it?

You can get the realm information by decode user's access token.
The "iss" (issuer) claim identifies the principal that issued the JWT.
This is decode example by JWT.io
I demo make two realms (realm1 and realm2)
each realm add single user (both user same username: user and password: 1234)
And call get access token and decode it by Python
import requests
import ast
import jwt
def get_issuer(realm, user_name, password):
url = 'http://localhost:8180/auth/realms/'+realm+'/protocol/openid-connect/token'
body = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : user_name,
'password': password
}
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
response = requests.post(url, data=body, headers=headers).content.decode('utf-8')
token = ast.literal_eval(response)['access_token']
# print(token)
decoded = jwt.decode(token, options={"verify_signature": False})
# print(decoded)
return decoded['iss']
print('realm1 with user -->', get_issuer('realm1','user','1234'))
print('realm2 with user -->', get_issuer('realm2','user','1234'))
get this output
$python get_realm.py
realm1 with user --> http://localhost:8180/auth/realms/realm1
realm2 with user --> http://localhost:8180/auth/realms/realm2
If you want to get all users of realm,
you can get this API with master realm's admin token
GET /{realm}/users

After talking with No_One, I realize he want to get a relam name by username.
I made 600 realms and 3 users for each realm. So total user 1.8K and each user unique username. (If may same username, we can extend username and e-mail). I made python program to create relams and users.
So I demo search realm by username with for loop.
Check username exist for every realm
For loop all of relams
{keycloak_URL}/auth/admin/realms/{realm_name}/users/?username={user_name}
if you want to get list of realms,
{keycloak-url}/auth/admin/realms
The realm name format is realm_0xxx
example) realm_0001, realm_0002, ..., realm_0600
each ream has three users
example) In realm_0001,
user01_in_realm0001,
user02_in_realm0001,
user03_in_realm0001
In realm_0002,
user01_in_realm0002,
user02_in_realm0002,
user03_in_realm0002
...
In realm_0600,
user01_in_realm0600,
user02_in_realm0600,
user03_in_realm0600
This Python code search user by for loop
import admin
import random
admin = admin.Admin()
token = admin.get_master_token()
random_realm_num = random.randint(1, 600)
random_user_num = random.randint(1, 3)
realm_name = "realm_{:04d}".format(random_realm_num)
user_name = "user{:02d}_in_realm{:04d}".format(random_user_num, random_realm_num)
print('random realm_name:', realm_name)
print('random user_name:', user_name)
found = False
for realm_index in range(1,600,1):
realm_name = "realm_{:04d}".format(realm_index)
if(admin.is_user_exist(token, realm_name, user_name)):
print('user_name:', user_name,' belong to',realm_name)
found = True
break
if (not found):
print('user_name:', user_name,'is not belong to any realms')
This admin class
from urllib import response
from urllib.error import HTTPError
import requests
import ast
import json
class Admin:
# Keycloak master realm URL
url = 'http://localhost:8180/auth/realms/master/protocol/openid-connect/token'
# Keycloak master credential
params = {
'client_id': 'admin-cli',
'grant_type': 'password',
'username' : 'admin',
'password': 'admin'
}
def get_master_token(self):
try:
response = requests.post(self.url, self.params, verify=False).content.decode('utf-8')
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
print('Keycloak container is not running, Please check your docker container!')
raise SystemExit
else:
return ast.literal_eval(response)['access_token']
def is_user_exist(self, token, realm_name, user_name):
url ='http://localhost:8180/auth/admin/realms/'+realm_name+'/users/?username='+user_name.replace(" ", "%20")
headers = {
'content-type': 'application/json',
'Authorization' : 'Bearer '+ str(token)
}
try:
response = requests.get(url, headers=headers)
# print (response)
# print (response.content)
# If the response was successful, no Exception will be raised
response.raise_for_status()
except HTTPError as http_err:
print(f'HTTP error occurred: {http_err}') # Python 3.6
except Exception as err:
print(f'Other error occurred: {err}') # Python 3.6
else:
# print('Success!')
# print(response.text)
if len(response.content) == 2: # []
return False
if (json.loads(response.text)[0]['username'] == user_name.lower()):
return True
else:
return False
Result
random realm_name: realm_0430
random user_name: user03_in_realm0430
user_name: user03_in_realm0430 belong to realm_0430
[Done] exited with code=0 in 21.248 seconds

We also encountered such problem but we finally implemented by this way:
create a new SPI and provide a new rest endpoint like '/realm-list'
it will return a list of realms that doesn't require admin privilege to access
provide a page to list and choose your realm and than click a button
forward current page to login page(the realm will reprenset in url path)
one thing needs to note, the backend of keycloak needs to check if user is logged in, we add a new cookie value to mark whether the user is logged in.

Related

django authenticate not working with user created by api , only work with user created by admin

i'm trying to generated token after login using drf. i'm using emailbackend for login with email and password but its not working with user created by api and with user created by admin its working
backends.py:
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
Token serializers:
class AuthCustomTokenSerializer(serializers.Serializer):
'''
Changing Token auth to use email instead username
'''
email = serializers.EmailField(label=_("Email"))
password = serializers.CharField(
label=_("Password",),
style={'input_type': 'password'},
trim_whitespace=False
)
def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')
print(email, password)
if email and password:
user = authenticate(username=email, password=password)
print("this is user", user)
# The authenticate call simply returns None for is_active=False
# users. (Assuming the default ModelBackend authentication
# backend.)
if not user:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg, code='authorization')
else:
msg = _('Must include "username" and "password".')
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
login view:
#csrf_exempt
#api_view(["POST"])
#permission_classes((AllowAny,))
def login(request):
serializer = AuthCustomTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, _ = Token.objects.get_or_create(user=user)
return Response({token: token.key}, status=status.HTTP_200_OK)
with admin login:
user login create by api:
register api:
Thanks, Great.
This means that authenticate(username=email, password=password) does not return a user.
Do you work with a degugger ? or may be add a
print(email, password) just after the auth call.
print what comes back from auth . print(authenticate(username=email, password=password))
My guess is that username is not email or somthing like that :)
Edit
How to debug:
login with admin user stop just before this line:
authenticate(username=email, password=password)
check and print the email and password
Do the same with API user check and print the email and password
see that values are the same .
login to django admin site check all premissions flag groups etc etc that are different between both users
try to login to admin page with the api user (set up the correct flags is_active etc)
try in the django manage.py shell or from admin user page to create new password for the api user and retest

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.

Request authorization do not work as expected

I'm trying to use the following:
auth = request.authorization
if not auth or not metrites.check( auth.username, auth.password ):
return metrites.authenticate()
and within module metrites i have:
def check( username, password ):
# This function is called to check if a username/password combination is valid
return username == 'nikos' and password == '*****'
def authenticate():
# Sends a 401 response that enables basic auth
return Response( 'Credentials of a registered user required!', 401, {'WWW-Authenticate': 'Basic realm="User!"'} )
But every time i type a user/pass combo it failes to accept it properly and is resking me.
Do i have something wrong?

how can repeat signup aws cognito [solve]

solve is here gist
when sign up in our app, sign up with aws cognito and send verify email with code.
if user close app not input verify, user have to re sign up. (saved in cognito userpool state UNCONFIRM)
and it occur two problem.
password may be changed (when re sign up)
renew verify code
my code is here python3 and warrant
#app.route('/signup/', methods=['POST'])
def signup():
u = Cognito(os.getenv('COGNITO_USER_POOL_ID'), os.getenv('COGNITO_CLIENT_ID'),
user_pool_region=os.getenv('COGNITO_REGION'))
u.add_base_attributes(name=user_name, email=user_email)
u.register(user_email, user_password)
return redirect(url_for('lobby'))
err code
botocore.errorfactory.UsernameExistsException: An error occurred (UsernameExistsException) when calling the SignUp operation: An account with the given email already exists.
how to re signup with renew password, and send new verify email
Thanks
oh, i solve
#app.route('/signup/', methods=['POST'])
def signup():
idp_client = boto3.client('cognito-idp')
'''
resp = idp_client.sign_up(ClientId=app_client_id,
Username=user_email,
Password=user_password,
UserAttributes=[{'Name': 'email', 'Value': user_email}])
'''
resp = idp_client.resend_confirmation_code(ClientId=os.getenv('COGNITO_CLIENT_ID'),
Username=user_email)
print(resp) #check result
return redirect(url_for('lobby'))
use boto3 i want (resend not acquire password)

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