The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Plack::Auth::SSO::ORCID - implementation of Plack::Auth::SSO for ORCID

SYNOPSIS

    #in your app.psgi

    builder {

        #Register THIS URI in ORCID as a new redirect_uri
        mount "/auth/orcid" => Plack::Auth::SSO::ORCID->new(
            client_id => "APP-1",
            client_secret => "mypassword",
            sandbox => 1,
            uri_base => "http://localhost:5000",
            authorization_path => "/auth/orcid/callback",
            error_path => "/auth/error"
        )->to_app;

        #DO NOT register this uri as new redirect_uri in ORCID
        mount "/auth/orcid/callback" => sub {

            my $env = shift;
            my $session = Plack::Session->new($env);
            my $auth_sso = $session->get("auth_sso");

            #not authenticated yet
            unless( $auth_sso ){

                return [ 403, ["Content-Type" => "text/html"], ["forbidden"] ];

            }

            #process auth_sso (white list, roles ..)

            #auth_sso is a hash reference:
            #{
            #    package => "Plack::Auth::SSO::ORCID",
            #    package_id => "Plack::Auth::SSO::ORCID",
            #    response => {
            #        content_type => "application/json",
            #        content => ""{\"orcid\":\"0000-0002-5268-9669\",\"token_type\":\"bearer\",\"name\":\"Nicolas Franck\",\"refresh_token\":\"222222222222\",\"access_token\":\"111111111111\",\"scope\":\"/authenticate\",\"expires_in\":631138518}
            #    },
            #    uid => "0000-0002-5268-9669",
            #    info => {
            #        name => "Nicolas Franck",
            #        first_name => "Nicolas",
            #        last_name => "Franck",
            #        email => "nicolas.franck@nowhere.com",
            #        location => "BE",
            #        description => "my biography",
            #        other_names => [ "njfranck" ],
            #        urls => [ { mysite => "https://mysite.com" } ],
            #        external_identifiers => []
            #    },
            #    extra => {}
            #}

            #you can reuse the "orcid" and "access_token" to get the user profile

            [ 200, ["Content-Type" => "text/html"], ["logged in!"] ];

        };

        mount "/auth/error" => sub {

            my $env = shift;
            my $session = Plack::Session->new($env);
            my $auth_sso_error = $session->get("auth_sso_error");

            unless ( $auth_sso_error ) {

                return [ 302, [ Location => $self->uri_for( "/" ) ], [] ];

            }

            [ 200, [ "Content-Type" => "text/plain" ], [
                "Something went wrong. User could not be authenticated against CAS\n",
                "Please report this error:\n",
                $auth_sso_error->{content}
            ]];

        };

    };

DESCRIPTION

This is an implementation of Plack::Auth::SSO to authenticate against a ORCID (OAuth) server.

It inherits all configuration options from its parent.

Remember that this module only performs these steps:

* redirect to ORCID authorize url

* exchange 1: exchange authorization code for access token ( orcid and name known )

  This delivers a hash containing orcid, name, access_token and a refresh_token.

* exchange 2: exchange access_token for person information

  This delivers a hash containing detailed information.

So we actually retrieved two hashes.

Those steps provide the following information:

* uid: derived from "orcid" from the first hash

* info.name: derived from "name" from the first hash

* other keys in "info" are extracted from the second hash

    * info.first_name: string

    * info.last_name: string

    * info.other_names: array of strings

    * info.email: first primary and verified email

    * info.description: string. Retrieved from "biography".

    * info.location: country code

    * info.url: array of objects

    * info.external_identifiers: array of objects

* extra: both hashes from the first and second call to ORCID are merged. For obvious reasons "orcid" is not present, and neither is "name" because it has a conflicting format between the two responses.

extra.access_token contains a code that conforms to the scope requested (see below).

If you want to request additional information from ORCID

CONFIG

Register the uri of this application in ORCID as a new redirect_uri.

DO NOT register the authorization_path in ORCID as the redirect_uri!

client_id

client_id for your application (see developer credentials from ORCID)

client_secret

client_secret for your application (see developer credentials from ORCID)

sandbox

0|1. Defaults to 0. When set to 1, this api makes use of http://sandbox.orcid.org instead of http://orcid.org.

public

0|1. Defaults to 1. 0 means you're using the member API.

scope

Requested scope. When not set, the parameter "public" will decide the value:

* public 1 : scope is "/authenticate"

* public 0 : scope is "/read-limited"

Please consult the ORCID to make sure that the parameters "public" and "scope" do not clash. (e.g. public is 1 and scope is "/read-limited")

ERRORS

Known ORCID errors are stored in the session key auth_sso_error ( Plack::Auth::SSO ).

"error" becomes "type"

"error_description" becomes "content"

Example:

    {
        package => "Plack::Auth::SSO::ORCID",
        package_id => "Plack::Auth::SSO::ORCID",
        type => "invalid_grant",
        content => "Reused authorization code: abcdefg"

    }

https://members.orcid.org/api/resources/error-codes

when it receives an non json response from ORCID, the type is "unknown", and content is set to the full return value.

AUTHOR

Nicolas Franck, <nicolas.franck at ugent.be>

SEE ALSO

Plack::Auth::SSO