Dynamic Route stopping when '#' appears in URL

I’m working on a sign in system for Azure AD. I’ve gotten to the point where Microsoft is returning a code and state in the url so I can then take that and do stuff. I’ve set up a dynamic route to catch what they send me. The problem is that their URL variable starts with a #. This stop dynamic routing for anything after the #, which is everything in this case as in the example below. I can’t even use a str.replace() because it treats everything after the # as commented out. If you copy and run the example below, you can move the # to anywhere in the dynamic portion of the URL and it will show everything up to that point. Is there anything that can be done to fix this?

Here’s my example code:

class State(rx.State):
    whole_arg: str
    callback_redir: str = "/callback/#code=1.AS4ADc4dZcjIOU4AJFPsT95G--SQZAYf-lMGQtYzhjOC03YmVhYmY5YWNhIn0&state=uawMRzrmaC&session_state=f61eb0e6-f104fd219df"


def index() -> rx.Component:
    return rx.container(
        rx.color_mode.button(position="top-right"),
        rx.button("Go!", on_click=rx.redirect(State.callback_redir)),
    )


def callback() -> rx.Component:
    return rx.container(
        rx.text(State.code_arg),
    )


app = rx.App()
app.add_page(index)
app.add_page(callback, route="/callback/[code_arg]")
1 Like

For Azure AD authentication, you should configure your redirect URI to use ? query parameters instead of # fragments

Hey @alek ,
I’ve since learned that is the fragment method and switched over to trying the query response mode instead. I’m still running into an issue where the incoming query from Microsoft is empty. I’ve found the rx.state.router.page.params and thought it would be in there, but there isn’t anything in there. Here’s the current code I’m testing below. It doesn’t get past the auth_state check in the handle_callback function. Should the page.params be where I’m looking? Is there another place or a setting I’m missing?

class State(rx.State):
    authenticated_user: bool = False
    user_info: str = ""
    session_state: str = ""  # Store the session state for comparison
    auth_session: dict = None  # Store auth session details

    def handle_login(self):
        # Generate a unique session state to track the request
        self.session_state = ''.join(random.choices(string.ascii_letters, k=16))

        # Start the auth code flow and store the session
        self.auth_session = msal_app.initiate_auth_code_flow(
            scopes=msal_config["scope"],
            redirect_uri=msal_config["redirect_uri"],  # Do not modify this
            state=self.session_state
        )

        if self.auth_session and 'auth_uri' in self.auth_session:
            auth_url = self.auth_session['auth_uri']
            return rx.redirect(auth_url)
        else:
            print("Error initiating auth code flow.")
            self.user_info = "Error during login initiation."

    def handle_callback(self):
        # Retrieve authorization response from Reflex's router page parameters
        auth_response = self.router.page.params

        # Compare session state from URL with the one stored in the state
        if self.session_state != auth_response.get('state'):
            print(f"State mismatch: {self.session_state} vs {auth_response.get('state')}")
            self.user_info = "Authentication failed due to state mismatch."
            self.authenticated_user = False
            return

        if not self.auth_session:
            print("Auth session not found or expired.")
            self.user_info = "Session expired or invalid request."
            self.authenticated_user = False
            return

        try:
            # Use MSAL to exchange the auth code for tokens
            result = msal_app.acquire_token_by_auth_code_flow(self.auth_session, auth_response)
            if "access_token" in result:
                self.user_info = "Authenticated successfully"
                self.authenticated_user = True
            else:
                error = result.get("error", "Unknown error")
                print(f"Error in token acquisition: {error}")
                self.user_info = f"Authentication failed: {error}"
                self.authenticated_user = False
        except ValueError as e:
            print(f"Error during authentication: {e}")
            self.user_info = "Authentication failed due to CSRF error"
            self.authenticated_user = False

def index():
    return rx.container(
        rx.button("Login with Microsoft", on_click=State.handle_login),
    )

def callback_page():
    return rx.container(
        rx.checkbox(checked=State.authenticated_user),
        rx.text(State.user_info),
        rx.text_area(str(State.router.page.params)),  # Display query params for debugging
    )

app = rx.App()
app.add_page(callback_page, "/callback", on_load=State.handle_callback)
app.add_page(index, "/")