Skip to content

Instantly share code, notes, and snippets.

@Vexcited
Created September 22, 2025 23:53
Show Gist options
  • Select an option

  • Save Vexcited/b13b71d469e190252a850f5fd4c181b6 to your computer and use it in GitHub Desktop.

Select an option

Save Vexcited/b13b71d469e190252a850f5fd4c181b6 to your computer and use it in GitHub Desktop.
Write your very own CAS to authenticate with PRONOTE!

Gather CAS IDs from PRONOTE

Let's open the PRONOTE client using administrative rights.

image

Let's click the image button on the right to add more columns to our view.

image

Drag to the right the following columns.

  • Id. connexion (PRONOTE username you'd use normally)
  • Id. PRONOTE-ENT (IDs to use in our CAS that links PRONOTE and our CAS)
  • Id. CAS (ID returned by our CAS, will be reused on later connections to quickly identify the user)

As you can see, we now have these values.

image

For the example, I'll authenticate to lea.bally account using our CAS. Let's note down her "Identifiant PRONOTE-ENT": A8BT6EGHDW3QFPXC

Create our own CAS

Nothing really interesting here, we're simply following specifications and examples from https://apereo.github.io/cas/7.2.x/protocol/CAS-Protocol-Specification.html.

type TICKET = string;
const tickets = new Map<TICKET, string>();

const assignTicketTo = (user: string): TICKET => {
  const randomData = randomBytes(32).toString("base64url");
  const ticket = `ST-${randomData}` as TICKET;
  tickets.set(ticket, user);
  return ticket;
};
// This is where PRONOTE will redirect us to!
.get("/cas/login", page, {
  detail: { summary: "credential requestor" },
  query: t.Object({
    service: t.String(),
  }),
})
// When we submit the form in GET /cas/login
// we should have a way to retrieve the ID we noted earlier!
// In this example, the ID is retrieved from the page (a simple input!) where I gave A8BT6EGHDW3QFPXC.
.post(
  "/cas/login",
  async ({ body, set }) => {
    const id = body.id;
    const service = new URL(body.service);

    // Let's redirect to <pronote>?ticket=ST-....
    const ticket = assignTicketTo(id);
    service.searchParams.set("ticket", ticket);

    set.status = 302;
    set.headers.location = service.href;

    return null;
  },
  {
    detail: { summary: "credential acceptor" },
    body: t.Form({
      id: t.String(), // this is "A8BT6EGHDW3QFPXC" !! 
      service: t.String(),
    }),
  }
)
// This is where the PRONOTE server will request to verify your ticket!
// My CAS ID here is cas-A8BT6EGHDW3QFPXC, I simply append `cas-` in front (SubjectName)
// I'll give the PRONOTE ID in the `pronoteid` CAS attribute.
.post("/cas/samlValidate", async ({ set, request }) => {
  const body = await request.text();

  const start = "<samlp:AssertionArtifact>";
  const end = "</samlp:AssertionArtifact>";

  const startIndex = body.indexOf(start);
  const endIndex = body.indexOf(end);

  const ticket = body.substring(startIndex + start.length, endIndex).trim();

  const id = tickets.get(ticket);
  set.headers["content-type"] = "text/xml";

  const instant = new Date().toISOString();
  tickets.delete(ticket);

  return `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header />
<SOAP-ENV:Body>
  <Response xmlns="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
  xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" IssueInstant="${instant}"
  MajorVersion="1" MinorVersion="1" Recipient="https://eiger.iad.vt.edu/dat/home.do"
  ResponseID="_cas_response_${id}">
    <Status>
      <StatusCode Value="samlp:Success">
      </StatusCode>
    </Status>
    <Assertion xmlns="urn:oasis:names:tc:SAML:1.0:assertion" AssertionID="_e5c23ff7a3889e12fa01802a47331653"
    IssueInstant="${instant}" Issuer="localhost" MajorVersion="1"
    MinorVersion="1">
      <AttributeStatement>
        <Subject>
          <NameIdentifier>cas-${id}</NameIdentifier>
          <SubjectConfirmation>
            <ConfirmationMethod>
              urn:oasis:names:tc:SAML:1.0:cm:artifact
            </ConfirmationMethod>
          </SubjectConfirmation>
        </Subject>
        <Attribute AttributeName="idpronote" AttributeNamespace="http://www.ja-sig.org/products/cas/">
          <AttributeValue>${id}</AttributeValue>
        </Attribute>
      </AttributeStatement>
      <AuthenticationStatement AuthenticationInstant="${instant}"
      AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password">
        <Subject>
          <NameIdentifier>cas-${id}</NameIdentifier>
          <SubjectConfirmation>
            <ConfirmationMethod>
              urn:oasis:names:tc:SAML:1.0:cm:artifact
            </ConfirmationMethod>
          </SubjectConfirmation>
        </Subject>
      </AuthenticationStatement>
    </Assertion>
  </Response>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
  `;
})

Let's run this server and go back to PRONOTE!

Connect the CAS to PRONOTE

Let's go into "Déléguer l'authentification" submenu.

image

Let's create a new CAS configuration from there!

image

Click the gear to open the configuration dialog, we'll use the following settings.

  • Mon ENT : Configuration Manuelle
  • URL du serveur CAS : Configuration personnalisée
  • URL d'authentification : <server>/cas/login
  • URL de validation : <server>/cas/samlValidate

Now, hit the "Paramètres" button on the top right corner and fill in the following settings.

  • Définition de l'utilisateur commun à CAS et PRONOTE : Utiliser l'identifiant utilisateur CAS (Subject)
  • Reconnaissance de l'utilisateur dans PRONOTE à la première connexion : Avec l'identifiant PRONOTE de l'utilisateur
  • Attribut CAS contenant l'identifiant PRONOTE : idpronote

Click "Valider" to submit those settings.

Make sure to check "Autoriser l'authentification directe par PRONOTE.net" so you can still log in the old way using ?login=true at the end of the URL.

Finally, hit "Valider" on this dialog and you're done configuring your CAS on PRONOTE.

Double click on the "Actif" cell to enable the CAS configuration.

Also, make sure to enable the CAS configuration for each webspace you want to enable CAS.

Now, publish!

See and enjoy!

Let's go to <pronote>/ and we'll get redirected to our hand made CAS. If we authenticate, we get correctly redirected to PRONOTE with our user authenticated!

If we go back to our PRONOTE client, we can see our cas-<id> value has been added to the CAS ID column!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment