Skip to content

Commit

Permalink
Adding Support For Back Channel Login (#643)
Browse files Browse the repository at this point in the history
Adding Support For CIBA
  • Loading branch information
kishore7snehil authored Jan 28, 2025
1 parent 9a32645 commit 3d5df93
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 0 deletions.
37 changes: 37 additions & 0 deletions auth0/authentication/back_channel_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any

from .base import AuthenticationBase


class BackChannelLogin(AuthenticationBase):
"""Back-Channel Login endpoint"""

def back_channel_login(
self, binding_message: str, login_hint: str, scope: str, **kwargs
) -> Any:
"""Send a Back-Channel Login.
Args:
binding_message (str): Human-readable string displayed on both the device calling /bc-authorize and the user’s
authentication device to ensure the user is approves the correct request.
login_hint (str): String containing information about the user to contact for authentication.
scope(str): "openid" is a required scope.Multiple scopes are separated
with whitespace.
**kwargs: Other fields to send along with the PAR.
Returns:
auth_req_id, expires_in, interval
"""
return self.authenticated_post(
f"{self.protocol}://{self.domain}/bc-authorize",
data={
"client_id": self.client_id,
"binding_message": binding_message,
"login_hint": login_hint,
"scope": scope,
**kwargs,
},
)
24 changes: 24 additions & 0 deletions auth0/authentication/get_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,27 @@ def passwordless_login(
"grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp",
},
)

def backchannel_login(
self, auth_req_id: str, grant_type: str = "urn:openid:params:grant-type:ciba",
) -> Any:
"""Calls /oauth/token endpoint with "urn:openid:params:grant-type:ciba" grant type
Args:
auth_req_id (str): The id received from /bc-authorize
grant_type (str): Denotes the flow you're using.For Back Channel login
use urn:openid:params:grant-type:ciba
Returns:
access_token, id_token
"""

return self.authenticated_post(
f"{self.protocol}://{self.domain}/oauth/token",
data={
"client_id": self.client_id,
"auth_req_id": auth_req_id,
"grant_type": grant_type,
},
)
78 changes: 78 additions & 0 deletions auth0/test/authentication/test_back_channel_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

import unittest
from unittest import mock

import requests
from ...exceptions import Auth0Error, RateLimitError

from ...authentication.back_channel_login import BackChannelLogin

class TestBackChannelLogin(unittest.TestCase):
@mock.patch("auth0.rest.RestClient.post")
def test_ciba(self, mock_post):
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")

g.back_channel_login(
binding_message="This is a binding message",
login_hint="{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }",
scope="openid",
)

args, kwargs = mock_post.call_args

self.assertEqual(args[0], "https://my.domain.com/bc-authorize")
self.assertEqual(
kwargs["data"],
{
"client_id": "cid",
"client_secret": "clsec",
"binding_message": "This is a binding message",
"login_hint": "{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }",
"scope": "openid",
},
)

@mock.patch("auth0.rest.RestClient.post")
def test_should_require_binding_message(self, mock_post):
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")

# Expecting an exception to be raised when binding_message is missing
with self.assertRaises(Exception) as context:
g.back_channel_login(
login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }',
scope="openid",
)

# Assert the error message is correct
self.assertIn("missing 1 required positional argument: \'binding_message\'", str(context.exception))

@mock.patch("auth0.rest.RestClient.post")
def test_should_require_login_hint(self, mock_post):
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")

# Expecting an exception to be raised when login_hint is missing
with self.assertRaises(Exception) as context:
g.back_channel_login(
binding_message="This is a binding message.",
scope="openid",
)

# Assert the error message is correct
self.assertIn("missing 1 required positional argument: \'login_hint\'", str(context.exception))

@mock.patch("auth0.rest.RestClient.post")
def test_should_require_scope(self, mock_post):
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")

# Expecting an exception to be raised when scope is missing
with self.assertRaises(Exception) as context:
g.back_channel_login(
binding_message="This is a binding message.",
login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }',
)

# Assert the error message is correct
self.assertIn("missing 1 required positional argument: \'scope\'", str(context.exception))



22 changes: 22 additions & 0 deletions auth0/test/authentication/test_get_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,25 @@ def test_passwordless_login_with_email(self, mock_post):
"scope": "openid",
},
)

@mock.patch("auth0.rest.RestClient.post")
def test_backchannel_login(self, mock_post):
g = GetToken("my.domain.com", "cid", client_secret="csec")

g.backchannel_login(
auth_req_id="reqid",
grant_type="urn:openid:params:grant-type:ciba",
)

args, kwargs = mock_post.call_args

self.assertEqual(args[0], "https://my.domain.com/oauth/token")
self.assertEqual(
kwargs["data"],
{
"client_id": "cid",
"client_secret": "csec",
"auth_req_id": "reqid",
"grant_type": "urn:openid:params:grant-type:ciba",
},
)

0 comments on commit 3d5df93

Please sign in to comment.