Skip to content

Token-authenticated API - #1121

Closed
AlexandreDoneux wants to merge 38 commits into
INGInious:mainfrom
AlexandreDoneux:submissions_api
Closed

Token-authenticated API#1121
AlexandreDoneux wants to merge 38 commits into
INGInious:mainfrom
AlexandreDoneux:submissions_api

Conversation

@AlexandreDoneux

@AlexandreDoneux AlexandreDoneux commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR adds two endpoints to retrieve submissions using a token and a way for a user to generate it.

The token can be generated from the user preferences, in the page "API token". It is implemented using a JWT, allowing an automatic expiration.

The endpoints :

  • /api/v0/token/courses/<courseid>/submissions
    • Returns the submissions of a course, and the data related to them.
  • /api/v0/token/courses/<courseid>/<taskid>/submissions
    • Returns the submissions of a particular task

Both are POST endpoint due to the use of parameters passed through a JSON body. Passing them as arguments or parameters through the url could become cumbersome.
That body can contain :

  • select: "all" (default), "best", "last" : select all submissions, the best submission per student, or the last submission per student
  • username: a list of usernames to filter the submissions. If none is provided (or it is empty), the submissions for all users are returned
  • format: "json" (default), "csv" : format of the response.

Yet to bee implemented :

  • Removing test endpoint
  • Add csv formatting for the endpoint's response
  • Add the JWT lifetime and secret in the webapp configuration
  • Work on api token page in the UI, multiple tokens with invalidation, token only visible once at generation
  • Store hash of token
  • store token secret and lifetime in config
  • Change endpoint names
  • Complete doc

@codacy-production

codacy-production Bot commented Jul 9, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 medium

Alerts:
⚠ 2 issues (≤ 0 issues of at least minor severity)

Results:
2 new issues

Category Results
Complexity 2 medium

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@anthonygego anthonygego left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a first pass of things that shoud be addressed before going further.

Comment thread inginious/frontend/pages/api/_api_page.py Outdated
Comment thread inginious/frontend/pages/preferences/apitoken.py Outdated
Comment thread inginious/frontend/pages/api/_api_page.py Outdated
Comment thread inginious/frontend/pages/api/_api_page.py Outdated
Comment thread inginious/frontend/templates/apitoken.html
Comment thread inginious/frontend/pages/preferences/apitoken.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py Outdated
@AlexandreDoneux
AlexandreDoneux force-pushed the submissions_api branch 2 times, most recently from fae1614 to be20b09 Compare July 27, 2026 14:34
@AlexandreDoneux

AlexandreDoneux commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

I have addressed your current remarks. I have on my end other questions I would like to submit to you :

  • endpoint names : I have for the moment used /api/v0/token in the endpoints I have added. However since we don't use the flask session anymore all endpoints use th token authentication and this distinction is not needed anymore. I will change that. But do you want to change the version of the api ?
  • POST input : I used the JSON input format for the endpoints I added. Should we stick to the form format ? It is necessary for the endpoint to add a submission for a file problem as JSON input does not support files. I therefore added both formats for that endpoint. But it might be better to stick to one format.
  • I kept a HS256 symmetric algorithm for JWT as it is simple and the INGInious "backend" is the only one encoding and decoding the JWTs. Is i enough or should we use an asymmetric algorithm ?

@anthonygego

Copy link
Copy Markdown
Member
  • endpoint names : I have for the moment used /api/v0/token in the endpoints I have added. However since we don't use the flask session anymore all endpoints use th token authentication and this distinction is not needed anymore. I will change that. But do you want to change the version of the api ?

Yes, I think we can, even if the interface doesn't change. That will invalide any old usage of the API. No need to preserve any retrocompatibility, the thing was broken for years.

POST input : I used the JSON input format for the endpoints I added. Should we stick to the form format ?

No JSON's probably good.

It is necessary for the endpoint to add a submission for a file problem as JSON input does not support files. I therefore added both formats for that endpoint. But it might be better to stick to one format.

This is not something bad I guess. This practice can be found in other software. For instance, in Grist, the endpoint for creating a new document supports the two body schema, json and multipart : https://support.getgrist.com/api/#tag/docs/operation/importDoc

The main requirement remains that the API is usable through standard librairies and methods.

I kept a HS256 symmetric algorithm for JWT as it is simple and the INGInious "backend" is the only one encoding and decoding the JWTs. Is i enough or should we use an asymmetric algorithm ?

I guess that's enough, indeed, as long as the symmetric algorithm is robust enough for a few years.

@AlexandreDoneux
AlexandreDoneux marked this pull request as ready for review August 6, 2026 12:02

@anthonygego anthonygego left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In summary, there are 3 things that need attention here:

  • The way the user is propagated between parent and children API classes.
  • Tokens and hashes sent in post forms.
  • The way submission input are served.

Comment thread inginious/frontend/pages/api/submissions.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py Outdated
Comment thread inginious/frontend/templates/apitoken.html Outdated
Comment thread inginious/frontend/templates/apitoken.html Outdated
Comment thread inginious/frontend/submission_manager.py
Comment thread inginious/frontend/submission_manager.py Outdated
Comment thread inginious/frontend/pages/api/_api_page.py Outdated
@anthonygego anthonygego changed the title Submissions api Token-authenticated API Aug 6, 2026
@nrybowski
nrybowski self-requested a review August 10, 2026 12:28
@nrybowski
nrybowski force-pushed the main branch 7 times, most recently from a26321f to 9835953 Compare August 11, 2026 12:48

@nrybowski nrybowski left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All in all, this PR requires some heavy lifting.

Comment on lines -146 to +143
flask_app.add_url_rule('/api/v0/auth_methods',
view_func=APIAuthMethods.as_view('apiauthmethods'))
flask_app.add_url_rule('/api/v0/authentication',
view_func=APIAuthentication.as_view('apiauthentication'))
flask_app.add_url_rule('/api/v0/courses', view_func=APICourses.as_view('apicourses.alias'),
flask_app.add_url_rule('/api/v1/courses', view_func=APICourses.as_view('apicourses.alias'),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bumping the API version is a breaking change that is not even documented in the PR description. Is the bump mandatory? If yes, document it extensively and maybe add dummy endpoints for the old version. That is the kind of silent breaking change we really do not want in production.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR we change from authentication using the flask session to a token. Using the endpoints as before will inevitably fail. Bumping the version feels to me like a good way to indicate such a big change.

Although I will grant you this is missing some documentation. Plus, when calling an non-existent url, INGInious send back an HTML page. I will implement specific messages for "/api" and "/api/v0" to indicate an endpoint is not available or is obsolete.

Should I keep legacy endpoints rather than returning an error ?

Comment thread inginious/frontend/pages/api/_api_page.py Outdated
Comment thread inginious/frontend/pages/api/_api_page.py Outdated
Comment thread inginious/frontend/pages/api/submissions.py
Comment thread inginious/frontend/pages/api/submissions.py
Comment thread inginious/frontend/templates/apitoken.html
Comment thread inginious/frontend/templates/apitoken.html Outdated
Comment thread inginious/frontend/app.py
Comment thread inginious/frontend/models/user.py Outdated
Comment thread inginious/frontend/submission_manager.py
@nrybowski

Copy link
Copy Markdown
Member
  • endpoint names : I have for the moment used /api/v0/token in the endpoints I have added. However since we don't use the flask session anymore all endpoints use th token authentication and this distinction is not needed anymore. I will change that. But do you want to change the version of the api ?

Yes, I think we can, even if the interface doesn't change. That will invalide any old usage of the API. No need to preserve any retrocompatibility, the thing was broken for years.

I'm uncomfortable with such a breaking change, we should deploy dummy endpoints documenting the API changes for any scripts out there in the wild that may still use the old (and maybe broken) API. Maybe some 301 with a payload rather than just plain 404 out of nowhere. Such endpoints should only be available for a single release.

@nrybowski

nrybowski commented Aug 28, 2026

Copy link
Copy Markdown
Member

Regarding to https://github.com/INGInious/INGInious/pull/1121/changes#r3880972260, we could generate a token per course upon admin user request, the authorized course id would be embedded within the JWT. Hence, we should not even load to course to ensure that the user is authorized to call the submission API for the specified course.
This could be implemented in the same manner as the webdav access token on the course administration page.
@anthonygego your thoughts?

@anthonygego

Copy link
Copy Markdown
Member

Regarding to https://github.com/INGInious/INGInious/pull/1121/changes#r3880972260, we could generate a token per course upon admin user request, the authorized course id would be embedded within the JWT. Hence, we should not even load to course to ensure that the user is authorized to call the submission API for the specified course. This could be implemented in the same manner as the webdav access token on the course administration page. @anthonygego your thoughts?

Yes that was planned for #1041 and #1042 but the work was not finished by the end of the internship. I was in favor of both user (mainly for submission) and course (mainly for stats) tokens. This can in practice just be implemented as scopes, I don't know. The main requirements is that students should be able to generate tokens too without being manageable by course admins.

But I really think that one feature must be implemented at a time in order to move on.

@AlexandreDoneux

Copy link
Copy Markdown
Contributor Author

I chose to hardcode the algorithm used for JWT. Allowing the choice for it could become messy due to different algorithms using different values (secrets, public keys, private keys). We would need to keep track of all the algorithms as well as all the different secrets and keys used to allow old tokens.

On top of that, changing from a symmetric to an asymmetric algorithm without changing the secret could lead to an algorithm-confusion attack.

@AlexandreDoneux

Copy link
Copy Markdown
Contributor Author

Closing this PR. We will implement the submissions API and the generation and use of tokens through smaller PRs. Starting with #1170.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants