|
|
|
# Synopsis
|
|
|
|
OpenID exists so that individual sites do not need to implement ad-hoc login systems. An **OpenID Provider (OP)** can be used to authenticate the user with a single account, which can be shared with individual sites (**Relying Party (RP)**)
|
|
|
|
OpenID exists so that individual sites do not need to implement ad-hoc login systems. An **OpenID Provider (OP)** can be used to authenticate the user with a single account, which can be shared with different sites.
|
|
|
|
|
|
|
|
[DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account) provides an implementation which plays both roles- the provider and the relying party. It enables federated user authentication, providing a single identity which can be used across various RPs and client-side applications
|
|
|
|
In essence [OpenID Connect (OIDC)](https://openid.net/connect/) means that you can use one identity to log into many sites, globally identifiable via a "WebFinger ID", for example `alice@aliceswebsite.com`.
|
|
|
|
|
|
|
|
# The Federated User
|
|
|
|
The repository [django-webid-oidc-provider](https://git.startinblox.com/djangoldp-packages/django-webidoidc-provider) provides the implementation of an OP, the provider of the identity, which works with both OIDC and with [Solid-OIDC](https://solid.github.io/authentication-panel/solid-oidc/), allowing users to login via a [Solid Pod](https://solidproject.org/users/get-a-pod).
|
|
|
|
|
|
|
|
A federated user is a user which can authenticate with multiple servers. This means that you can login to `aliceswebsite.com` as a user from `bobswebsite.com`. For an introduction to linked data, please see [the W3C's primer](https://www.w3.org/TR/ldp-primer/)
|
|
|
|
|
|
|
|
DjangoLDP achieves this using [OpenID Connect (OIDC)](https://openid.net/connect/). In OIDC users are globally identifiable via a "WebFinger ID". An example is `alice@aliceswebsite.com`, where `alice` is the username, hosted by the provider `aliceswebsite.com`. Using OIDC Alice can authenticate with `aliceswebsite.com`, authorising `bobswebsite.com` to make use of her account
|
|
|
|
|
|
|
|
When implementing authentication in your own application, you have two options:
|
|
|
|
* [Using or extending DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp/wikis/guides/authentication#using-djangoldp-accounts-authentication), a DjangoLDP package modelling federated users
|
|
|
|
* [Using your own user model](https://git.startinblox.com/djangoldp-packages/djangoldp/wikis/guides/authentication#using-your-own-user-model) & defining the authentication behaviour yourself
|
|
|
|
|
|
|
|
Regardless of the choice you make, DjangoLDP will extend your user model to include a method `webid` which returns uri to identify your user, for example `https://aliceswebsite.com/users/alice`. This is used in `LDPSerializer` so that views are returned in linked-data format, describing the semantics and location of resources in the response
|
|
|
|
[DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account) is a Solid-Pod provider, and it's also the implementation of a **Resource Server**. That means that including this package in your repository, it will handle the authentication and user management for a Linked Data and Solid application.
|
|
|
|
|
|
|
|
# Using DjangoLDP-Account's Authentication
|
|
|
|
|
|
|
|
It's recommended that you use or extend DjangoLDP-Account, as out of the box it provides all you need to have federated users which can log in from multiple sources, implementing both the endpoints for an OIDC provider and the Relying Party
|
|
|
|
It's recommended that you use or extend DjangoLDP-Account, as out of the box it provides all you need to have federated users which can log in from multiple sources, implementing both the endpoints for an OIDC provider and the Resource Server.
|
|
|
|
|
|
|
|
If you want to use DjangoLDP-Account, but you don't need to understand its internal workings, please refer to the [DjangoLDP-Account Readme](https://git.startinblox.com/djangoldp-packages/djangoldp-account) which has a step-by-step on installation and usage
|
|
|
|
|
|
|
|
## Migrating From an existing user model
|
|
|
|
|
|
|
|
In Django, migrating the user model can be a delicate task. From the [Django documentation](https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#changing-to-a-custom-user-model-mid-project):
|
|
|
|
In Django, migrating the user model can be a delicate task. From the [Django documentation](https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#changing-to-a-custom-user-model-mid-project):
|
|
|
|
|
|
|
|
> Changing AUTH_USER_MODEL after you’ve created database tables is significantly more difficult since it affects foreign keys and many-to-many relationships, for example. Due to limitations of Django’s dynamic dependency feature for swappable models, the model referenced by AUTH_USER_MODEL must be created in the first migration of its app (usually called 0001_initial); otherwise, you’ll have dependency issues
|
|
|
|
|
|
|
|
# How It Works
|
|
|
|
|
|
|
|
This part of the guide is intended for developers who may need to understand DjangoLDP-Account's internal workings, as opposed to those who simply need to use it
|
|
|
|
|
|
|
|
## The OIDC Workflow
|
|
|
|
|
|
|
|
The underpinning technology for DjangoLDP-Account is the OpenID Connect (OIDC) workflow. It's necessary to understand this in order to understand how our implementation works:
|
|
|
|
* [Implementation in PyOIDC, used by DjangoLDP-Account](https://pyoidc.readthedocs.io/en/latest/examples/rp.html)
|
|
|
|
* [Example Workflow](https://github.com/solid/webid-oidc-spec/blob/master/example-workflow.md)
|
|
|
|
|
|
|
|
## The Relying Party (RP)
|
|
|
|
|
|
|
|
### Login
|
|
|
|
|
|
|
|
Login can be completed on two levels, the local level (using the local server), or the federated level (using a specified or discovered OIDC provider)
|
|
|
|
|
|
|
|
A [template](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/templates/registration/login.html) is defined which implements a login form with options for logging in to the local server, logging in with an arbitrary OIDC provider and for registering a new account
|
|
|
|
|
|
|
|
#### Local Login
|
|
|
|
|
|
|
|
DjanogLDP-Account defines a view `LDPAccountLoginView`, which is a simple implementation of the LoginView from [django-registration](https://django-registration.readthedocs.io/en/3.1/). Also from django-registration, this view is connected on the url path `/auth/login/`
|
|
|
|
|
|
|
|
#### Federated Login
|
|
|
|
|
|
|
|
DjangoLDP-Account defines a view `RPLoginView`, and provides a url for this view at `/oidc/login/`. The view leverages an `RPLoginEndpoint` class which defines the behaviour of the relying party's login system. [source code](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/rp_login.py)
|
|
|
|
|
|
|
|
The key function is `op_login_url`, which resolves the URL with which to make the authentication request. Login can be completed by providing a URL to the provider, or by providing an email or WebFinger ID. In the latter two cases, provider-discovery will be necessary (see 2.1 of the [Example Workflow](https://github.com/solid/webid-oidc-spec/blob/master/example-workflow.md))
|
|
|
|
|
|
|
|
`op_login_url` configures the callback redirect to the view `RPLoginCallBackView`, which uses another class defined in `rp_login.py`, `RPLoginCallBackEndpoint`, calling the `initial_url()` function when it receives the request. Here resides the logic to perform the login
|
|
|
|
|
|
|
|
## The OIDC Provider (OP)
|
|
|
|
|
|
|
|
During issuer discovery, the RP uses the host from the webID (e.g. `example.com` in the webID `alice@example.com`) and queries it with the resource `https://example.com/.well-known/webfinger?resource=acct%3Aalice%40example.com&rel=http%3A%2F%2Fopenid.net%2Fspecs%2Fconnect%2F1.0%2Fissuer`
|
|
|
|
|
|
|
|
DjangoLDP provides this functionality leveraging an external library, [django-oidc-provider](https://django-oidc-provider.readthedocs.io/en/latest/). It defines a `WebFingerEndpoint` which can be found [here](https://git.startinblox.com/djangoldp-packages/djangoldp/blob/master/djangoldp/endpoints/webfinger.py), and a `WebFinger` class which DjangoLDP-Account [builds on](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/webfinger.py)
|
|
|
|
|
|
|
|
# Using Your Own User Model
|
|
|
|
|
|
|
|
It's not required to use or extend the `LDPUser` user model from DjangoLDP-Account, although doing so is the recommended option because DjangoLDP-Account defines the behaviour of an OIDC Provider & Relying Party for you
|
|
|
|
It's not required to use or extend the `LDPUser` user model from DjangoLDP-Account, although doing so is the recommended. Supporting OIDC and Solid-OIDC will be necessary in order to be interoperable on the semantic web.
|
|
|
|
|
|
|
|
If you can't use this class or don't want to then you don't have to, provided you meet the following requirements:
|
|
|
|
|
| ... | ... | @@ -75,33 +31,33 @@ For federated login to work, your user model must extend `DjangoLDP.Model`, or d |
|
|
|
```
|
|
|
|
urlid = LDPUrlField(blank=True, null=True, unique=True)
|
|
|
|
```
|
|
|
|
If you don't include this field, then all users will be treated as users local to your instance
|
|
|
|
If you don't include this field, then all users will be treated as users local to your instance.
|
|
|
|
|
|
|
|
**Why?**
|
|
|
|
|
|
|
|
`LDPUrlField` is based on Django's models.URLField and is defined [here](https://git.startinblox.com/djangoldp-packages/djangoldp/blob/master/djangoldp/fields.py)
|
|
|
|
The `urlid` is an identifier for the resource, for example `https://aliceswebsite.com/users/alice/`. It can be generated at runtime for a local user (we could build the example from the username `alice` and the users endpoint), but when a user from another site, `https://bobswebsite.com/users/bob/` logs into the server, a local copy will be created, which needs to store bob's `urlid`
|
|
|
|
The `urlid` is an identifier for the resource, for example `https://aliceswebsite.com/users/alice/`. It can be generated at runtime for a local user (we could build the example from the username `alice` and the users endpoint), but when a user from another site, `https://bobswebsite.com/users/bob/` logs into the server, a local copy will be created, which needs to store bob's `urlid`.
|
|
|
|
|
|
|
|
## Implementing the OIDC Provider (OP)
|
|
|
|
|
|
|
|
If you would like the users of your site to be able to log in to other sites using their account, you will need to implement an OIDC Provider
|
|
|
|
If you would like the users of your site to be able to log in to other sites using their account, you will need to implement an OIDC Provider.
|
|
|
|
|
|
|
|
This can be achieved by extending the [webfinger endpoint in DjangoLDP](https://git.startinblox.com/djangoldp-packages/djangoldp/blob/master/djangoldp/endpoints/webfinger.py). An example implementation [can be found in DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/webfinger.py)
|
|
|
|
This can be achieved by extending the [webfinger endpoint in DjangoLDP](https://git.startinblox.com/djangoldp-packages/djangoldp/blob/master/djangoldp/endpoints/webfinger.py). An example implementation [can be found in DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/webfinger.py).
|
|
|
|
|
|
|
|
OIDC issuer discovery is achieved on the url `.wellknown/webfinger/`, passing the Webfinger ID of the user. The provider returns the information required by the Relying Party, for example DjangoLDP-Account extends the returning of information on the issuer for the account
|
|
|
|
OIDC issuer discovery is achieved on the url `.wellknown/webfinger/`, passing the Webfinger ID of the user. The provider returns the information required by the Relying Party, for example DjangoLDP-Account extends the returning of information on the issuer for the account.
|
|
|
|
|
|
|
|
You can subclass `WebFinger`, overriding the `response` method to add information to the response for a given user. The `response_dict` parameter may already contain information before you return it, as DjangoLDP automatically passes it to each subclass of `WebFinger` before returning it from the view
|
|
|
|
You can subclass `WebFinger`, overriding the `response` method to add information to the response for a given user. The `response_dict` parameter may already contain information before you return it, as DjangoLDP automatically passes it to each subclass of `WebFinger` before returning it from the view.
|
|
|
|
|
|
|
|
More is happening under the surface, as DjangoLDP comes packaged with [django-oidc-provider](https://django-oidc-provider.readthedocs.io/en/latest/) which handles much of the workflow
|
|
|
|
More is happening under the surface, as DjangoLDP comes packaged with [django-webid-oidc-provider](https://git.startinblox.com/djangoldp-packages/django-webidoidc-provider) which implements much of the workflow.
|
|
|
|
|
|
|
|
## Implementing the Relying Party (RP)
|
|
|
|
## Implementing the Resource Server (RS)
|
|
|
|
|
|
|
|
If you would like users from other sites to log into your site, then you will need to implement the endpoints of the Relying Party (RP)
|
|
|
|
If you would like users from other sites to log into your site, then you will need to implement the endpoints of the Resource Server.
|
|
|
|
|
|
|
|
An example implementation of the Relying Party endpoint [can be found in DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/rp_login.py). When the user submits their webfinger ID or OIDC provider to the [login form](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/templates/registration/login.html), the endpoint utilises [PyOIDC](https://pyoidc.readthedocs.io/en/latest/examples/rp.html) to discover the issuer of the provider, and provides the callback function in this endpoint to log the user in once authenticated
|
|
|
|
An example implementation of the Resource Server [can be found in DjangoLDP-Account](https://git.startinblox.com/djangoldp-packages/djangoldp-account/blob/master/djangoldp_account/endpoints/rp_login.py). When the user submits their webfinger ID or OIDC provider through a login form, the endpoint will discover the issuer of the provider, and provides the callback function in this endpoint to log the user in once authenticated.
|
|
|
|
|
|
|
|
It's important to note that once the federated user has successfully logged in, their urlid should be set to that of their OIDC provider, so that their id is maintained as from the distant source
|
|
|
|
|
|
|
|
## Registering with the Django Admin
|
|
|
|
|
|
|
|
When using a custom user model in Django, it's common to register it with the admin via Django's `UserAdmin` class. We offer an extension to this class as `djangoldp.admin.DjangoLDPUserAdmin` which provides the same functionality with the [DjangoLDP admin dependencies](https://git.startinblox.com/djangoldp-packages/djangoldp#using-djangoldp) and ensures that federated fields (such as urlid) are included in the field sets, allowing administrators to easily create external resources |
|
|
\ No newline at end of file |
|
|
|
When using a custom user model in Django, it's common to register it with the admin via Django's `UserAdmin` class. We offer an extension to this class as `djangoldp.admin.DjangoLDPUserAdmin` which provides the same functionality with the [DjangoLDP admin dependencies](https://git.startinblox.com/djangoldp-packages/djangoldp#using-djangoldp) and ensures that federated fields (such as urlid) are included in the field sets, allowing administrators to easily create external resources. |
|
|
\ No newline at end of file |