URL woes. Small subset of URLs returning 404s

Hello all,

I’ve run into an issue with my URLs which I cannot resolve. It’s probably something trivial but I think I’ve been staring at the problem so long that I’ve overlooked something simple.

I have added a new app called cas_auth and it is configured in my settings.py and all looks ands *runs well.

Note also that APPEND_SLASH = True

The issue is simply that I am getting 404s when accessing URLs which should be there and for which the configuration of the requisite URL files looks sound, at least to me.

In my root urls.py I have the following (this is a very small preview of the entire file).

CAS URLs issue

urlpatterns = [
    path("api/v1/cas/", include("cas_auth.urls")),   # this has never worked. It is the first item in the list
    path(f"api/v1/case-builder/", include("case_builder.urls")),   # this one has stopped working
    ...
]

The cas_auth.urls.py looks like this:

from django.urls import path
import django_cas_ng.views


urlpatterns = [
    path("login", django_cas_ng.views.LoginView.as_view(), name='cas_ng_login'),
    path("logout", django_cas_ng.views.LogoutView.as_view(), name='cas_ng_logout'),
    path("", django_cas_ng.views.LoginView.as_view(), name='cas_ng_login_default'),
]

The issue with my CAS urls is that none of the following URLs work:

  • /api/v1/cas/login
  • /api/v1/cas/logout
  • /api/v1/cas
  • /api/v1/cas/

All return a 404. The Django debug in the browser even lists the URLs above as URLs it has searched.

Case Builder URLs issue

The root of my case builder URLs have also stopped working.

The case_builder app has been up and running for some weeks. When this issue arose, I’m not sure, but it must have been recently.

  • /api/v1/case-builder

All other URLs do work (this list is not exhaustive), e.g.

  • /api/v1/case-builder/image
  • /api/v1/case-builder/create-case
  • /api/v1/case-builder/case/str:case

My case_builder.urls

urlpatterns = [
    path("video", VideoCreatorView.as_view(), name="video_builder"),
    path("image", ImageCreatorView.as_view(), name="image_builder"),
    path("audio", AudioCreatorView.as_view(), name="audio_builder"),
    path("case/<str:case>", CaseBuilderEntireCaseView.as_view(), name="case_builder_entire_case"),
    path("create-case", CaseReplicatorView.as_view(), name="case_builder"),
    path("my-cases", AllMyCasesBuilderView.as_view(), name="all_my_cases_case_builder"),
    path(
        "my-cases/<str:name>",
        MyCasesByGroupBuilderView.as_view(),
        name="cases_by_group_case_builder",
    ),
    path("", include(router.urls)),
    path("<str:case>", CaseBuilderEntireCaseView.as_view(), name="case_builder_entire_case1"),
]

One thing to note is that I am using DRF and in my config.url and my case_builder.urls there are some URLs using the DRF SimpleRouter. These are working just fine.

At this stage I am completely at a loss as to what the issue is. I’m hoping a clever clogs with fresh eyes might be able to identify the issue.

If you take DRF out of the picture, do those URLs work? (That would help to determine if the issue might be somewhere other than what you’ve posted here. FWIW, we also use django_cas_ng as our cas client. Also FWIW, we have our login and logout urls defined with the trailing slash.)

If you have django-extensions installed, you can run python manage.py show_urls. That can sometimes be helpful when debugging routing issues.

1 Like

Hi Tim, Ken,

Well I don’t know what happened by when I tried the URL this morning it worked. I’ve no idea why but I did find one error in my URLs but that was after the CAS login worked. It was a long day at my real job yesterday so I’ll put it down to temporary madness.

Tim, how have I never heard of Django Extensions? What a tool! Thank you.

Ken, very interesting to hear about your use of django-cas-ng. I’ve got it up and running against their Heroku test app and will be testing it against a well know US university soon. If we have only one university working with us who uses CAS, life will be very straightforward. However, the chance of that happening are really very slim. I’m investigating how I will create my own logic by subclassing the django-cas-ng LoginView and some of their methods in utils.py. Essentially I want to be able to redirect a user to their CAS login page based on the domain of their email address. Once they’ve logged in, I need to return access and refresh tokens to the JS frontend. Looks like I’ve got a busy couple of weeks of post-day job work ahead of me.

Once thought I had is, if I get the above working then it might be of value to propose an improvement to django-cas-ng to allow it to support multiple CAS servers. CAS isn’t something I’ve come across in Europe and I’m told that it is largely a North American thing, but I would think the use case of authenticating against multiple different CAS servers wouldn’t be unique to my use case. Do you authenticate against multiple CAS servers, or just the one? Do you think that if I get my end up and running it would be of use to django-cas-ng or perhaps beyond the scope of the package?

Cheers Ken, cheers Tim.

C

Conor,

I’ve never seen a case where I’d want to authenticate against multiple CAS servers. It seems kinda “backwards” to me.

A cas server can validate credentials against multiple backends, based on the originating URL. (Well, technically, it doesn’t need to be a URL, it can be any arbitrary “service name”, but 99+% of the time it’s a URL.)

I don’t know if you’ve run across this doc yet, but I found it extremely helpful when trying to learn about cas: CAS - CAS Protocol

Briefly, A person navigates to a web site that authenticates through cas. The web site redirects them to the cas server, passing the server a “service” name. (Typically a URL - more typically, the URL that the user gets transferred to after they have been authenticated.) So the key here is that CAS is capable of validating credentials against multiple backends, based upon the service name.

We use a modified version of django-cas-server as our current cas server. I think I know the code well enough to say that you could modify it to select different templates based upon the service name passed to it.

But keep in mind that it’s CAS that presents the login page, not the application protected by CAS. The person going to a URL on your site to a protected page doesn’t get the opportunity to enter an email address before being redirected.

Cheers!
Ken

Hi Ken,

Thank you for being so helpful.

Have I understood you correctly when I present the following:

  • I must authenticate students to their own institution’s CAS server. I can have students from x number of universities where each university has their own CAS server
  • I could, instead of hacking djang-cas-ng client as I described, build my own cas-server with django-cas-server which in turn can have multiple backends, i.e. one for each university.

Our app is for better and for worse, an SPA. My original thinking was that we would, like many services, ask the user to enter their username first, and based on that, redirect them to their CAS service for login. One thing to note is that a user must have a local account with us. This is largely for compliance reasons but also to ensure that only account holders can access the service, not the entire student base and teaching staff from one particular university. Given we use tokens for auth, I was going to override the Django HTTPResponse with a DRF JSON Response to return an access and refresh token to the client.

I should say that my original thinking was inspired by Django-Allauth as they support multiple OAuth providers and use a table in the DB which consists of a UserID and a ProviderID

The above is/was my current thinking and is a rough draft of my architecture. However, if I have understood you correctly and I can have multiple CAS servers behind my own CAS server, then I think that is the way to go.

I’ll go and study that CAS Protocol document - thank you - and see if I can’t make heads or tails of it.

Cheers,

Conor

This is a situation where it might be helpful to mentally separate “authentication” and “authorization” as two separate functions.

“Authentication” is the process of a person identifying themselves and providing credentials proving their identity. “Authorization” is the process of deciding whether or not that person is allowed to access a particular resource.

The purpose of CAS is to physically separate those two functions. CAS provides authentication services only. When a person has been authenticated, CAS notifies the requesting application that “User XYZ” has logged on. It’s up to the individual applications to decide whether or not User XYZ can access any particular page.

As a result of this, what CAS provides back to the application is the name of the user who has authenticated. What the django_ng_client does with that is then map that name back to a local User object for subsequent authorization throughout your system.

This is potentially a problem if there are any “naming conflicts” between services. It is possible for two people to have the same “CAS Username” in two different organizations - where one is a user of your service and the other isn’t.
The problem is that you don’t have control over the username supplied to you from CAS - and it’s not necessarily the username that the person logs in as. For example, I could configure a CAS server such that I log on to CAS as KenWhitesell, but CAS reports my username as KW12345 - because that is the username known throughout the systems “behind” that CAS instance.

Another factor in this is that every CAS system needs to be configured to allow your app to authenticate through it. This service name that I referenced earlier also works as a filter for CAS. CAS will not authenticate credentials from service names unknown to it. (I’m assuming you’re already working with the Universities for them to make those changes.)

(I’ve been working with CAS in our environment off-and-on for about 4 years now - I’m happy to help if you have any questions.)

Ken

1 Like

Hi Ken,

Apologies for the delay in replying. I meant to do so on Saturday morning and then life happened - it wasn’t all bad!.

I have started a new thread should you wish to continue our chat. I thought it was better to put it in a well-named thread so that other interested parties can find it if and when they wish.

Cheers,

Conor

Hi Conor!

I’ve seen the other topic and am crafting a response.

But on the meta-discussion: No worries, and certainly no apologies are required, expected, or even warranted. (Let’s be real here, and I hope you take this in the good-natured spirit in which it’s intended, but this is your topic, not mine. The conversation continues at whatever pace you deem appropriate or necessary.)

Cheers!
Ken