LOOK4 ID
LOOK4 ID provides
authorization for accessing web services, including LOOK4 Optics web
services, based on the OAuth 2.0 standard,
single-sign-on authentication for the LOOK4 Optics portal and
third-party web sites and applications, based on the OpenID Connect
(OIDC) standard.
OAuth and OpenID Connect (OIDC)
Why?
OAuth 2.0 is a delegation protocol, allowing a software client to act on
behalf of a user with limited access rights, without impersonating the
user.
In contrast, providing a software client (or third-party web service)
with the user’s password would give the software client or third-party
service full control over the user account and all related resources. It
is also a dangerous practice since clients need to store the user’s
password; the password would be compromised as soon as a client software
or service is compromised. This and many other issues with this
password-sharing antipattern are addressed with the use of OAuth.
The OAuth and OpenID Connect standards are widely used, e.g. for “Sign
in with Google” and many similar services. Therefore, extensive tooling
and documentation is available, and many developers, especially web and
mobile developers, are already familiar with it.
OAuth and OIDC terms and their meaning in the context of LOOK4 ID
LOOK4 ID acts as the authorization server and Identity Provider
(IdP).
Resource owners are LOOK4 ID users. Access rights depend on the
assigned company in LOOK4 Optics, e.g. eye care professionals (ECPs)
might be allowed to download catalogs, place orders, or retrieve
order status for previously submitted orders; whereas a user belonging
to a supplier company might be allowed to publish catalog updates.
Protected resources are web services (APIs), e.g. the LOOK4 Optics
API.
An OAuth client is a piece of software that accesses the protected
resource on behalf of the resource owner. For example, it could be
a practice management software, the portal of a home delivery service
provider, a POS mobile app, etc.
A relying party is a web application that outsources its user
authentication to LOOK4 ID. Such a website is also an OAuth client.
Scopes are used for fine-grained access control and define subsets
of functionality of a protected resource.
Scopes allow to limit what a client can do on behalf of the user.
Examples of subsets of
functionalities of the LOOK4 Optics API would be “read catalogs” or
“place orders” (see also documentation of the full list of scopes
used). Clients can request a set of scopes, and the resource owner
can decide whether to allow this request (or a subset of it). This
user interaction is called consent. The resource owner’s consent is
valid forever (unless actively revoked); this property of OAuth is
called Trust on first use (“TOFU”).
Authorization Token: A short-lived (1 hour) token that can be added
as Authorization header to all LOOK4 Optics web service requests.
This is a called a bearer token. Client applications can treat
bearer tokens as arbitrary strings that just need to be remembered
and passed on the protected resource.
Refresh Token: A long-lived token that can be stored by a client.
Used to get a new authorization token from the authorization server
without requesting the user to login to LOOK4 ID again, or even
without the user being present. Refresh tokens in LOOK4 ID have a
sliding expiration of 1 year, i.e. every time the refresh token is
used, its expiration date is updated to one year from now. If a
refresh token is not used for 1 year, the client would have to
redirect the resource owner to LOOK4 ID for a new login. A client
allowed to use refresh tokens is called Offline client.
Authorization Flow or Grant Type: A protocol or workflow for how
a client can obtain tokens from the authorization server. Supported
flows are covered below.
Federation: A scenario where LOOK4 ID trusts a third-party
authorization server, e.g. an existing portal for eye care
professionals created by a supplier or a retail chain. This can
enable the third-party portal (and related applications) to make use
of resources protected by LOOK4 ID (at least parts of it) without
requiring their end-users to create a LOOK4 ID account. Please
contact our support if you are interested in this scenario as this
(using a custom grant and client assertions) is not covered by
our public documentation and needs a custom implementation on both
ends.
OAuth in depth
If you are the author of a client software, you do not necessarily need
a deep understand of OAuth. If you still want to dig deeper, the
following resources can be a good start:
OAuth and OIDC libraries
We recommend using an authentication library or framework implementing
OAuth2 for communication with the authorization server. Although this is
not strictly necessary, it will make it easier to follow best practices
and achieve a higher level of security, while writing less code
yourself.
See the OAuth 2.0 web site for a list of client libraries for many
frameworks and programming languages
When using OIDC for authentication purposes, using a library
implementing the full OIDC protocol (including discovery, token
validation etc.) is inevitable.
See the OpenID website for a list of certified OpenID Connect relying
party libraries:
Implementations known to work well with LOOK4 ID (please let us know of
your experiences so that we can extend this list over time and keep it
updated):
Registering client applications
Client applications need to be registered with LOOK4 and get a unique
client ID assigned.
A self-service portal for managing client applications in LOOK4 ID is
planned; until it becomes available, please contact support@look4.de for
registering or updating clients. Thank you very much!
For all the client types, please include the following information in
your registration request:
Further optional information:
Include multiple languages for display name and description if your
software is available in multiple languages.
Additional information is required depending on the grant type or
“authorization flow”.
Types of Client Applications and suitable “Authorization Flows”
Browser-based JavaScript Applications
Supported Flows
If you can use a library that supports it, please use the
“Authorization code” flow (grant type authorization_code)
without a client secret and with the
PKCE OAuth extension (RFC 7636).
If this is not possible, you can use the “Implicit” flow
(grant type implicit),
but please note that it might be necessary in the future
to deprecate this flow due to security considerations.
This type of client cannot (and doesn’t need to) be an “offline client”
and cannot use refresh tokens.
Registration
For registering this kind of client application, in addition to the
general information listed above (“Registering client applications”),
please include the following information in your request:
Grant type (either authorization_code or implicit)
At least one Redirect URI: The redirect URI used in your
authorization requests must match exactly one of these, including
protocol, port number etc. Please remember to include URIs also for
your development and testing environments if applicable.
CORS origin URIs, i.e. protocol, domain and port number (in case of non-default port numbers).
Please remember to include URIs also for your development and testing environments if applicable.
Post-logout redirect URIs (optional):
If your client wants to offer a combined sign-out from the application and LOOK4 ID,
and an automatic redirection back from the LOOK4 ID logout page to the application,
you can specify one or multiple allowed redirect URIs for that purpose.
Please remember to include URIs also for your development and testing environments if applicable.
Native Desktop and Mobile Clients
Supported Flows
Please use the “Authorization code” flow (grant type authorization_code)
without a client secret and with the
PKCE OAuth extension (RFC 7636).
Registration
For registering this kind of client application, in addition to the
general information listed above (“Registering client applications”),
please include the following information in your request:
Offline access required? Whether long-lived refresh tokens will be provided or
not. Please note that all sign-ins requesting offline access need user consent.
At least one Redirect URI: The redirect URI used in your
authorization requests must match exactly one of these, including
protocol, port number etc. Please remember to include URIs also for
your development and testing environments if applicable.
URIs in the form of http://127.0.0.1:1234, where 1234 can be any valid port
number, are accepted by default; therefore, for native clients, you
can dynamically choose a free port for the back channel.
If you can make use of this pattern, no extra Redirect URI is needed.
Choosing the browser for the “front channel” interactions
For the actual LOOK4 ID login and consent, it is strongly recommended to
use the system browser instead of an embedded browser window (web view).
Firstly, this offers a better user experience, as the user is more
likely to be already signed into LOOK4 ID in the default browser,
therefore doesn’t need to type the password again. Secondly, user login
credentials should not be exposed to client applications, which could be
possible with using an embedded browser, as the hosting application has
full control over it.
Low-level Client Implementation
Just in case that you really have to implement this without making use
of a OAuth library or framework, or you want to get an idea of what is going
on behind the scenes, these are the steps performed to obtain the token
for an application with “offline” access:
Do not forget URL encoding for all URL parameters!
To perform the initial authentication (authorization code flow with PKCE extension):
Start a HTTP listener on a free local port, e.g. http://127.0.0.1:43210.
Create a random 32 byte array, apply web-safe BASE64 encoding and store the resulting string in variable state.
Create another random 32 byte array, apply web-safe BASE64 encoding, and store the resulting string in variable code_verifier.
Calculate the SHA256 hash of the code_verifier string, and store it as a web-safe BASE64 string as code_challenge.
Launch the system browser to the following LOOK4 ID URL:
GET /connect/authorize?
client_id=yourclientid&
scope=offline_access scope1 scope2 scope3& //(see list of scopes below)
response_type=code&
redirect_uri=http://127.0.0.1:xxxxx&
state=state-variable&
code_challenge=code_challenge-variable&
code_challenge_method=S256
User logs into LOOK4 ID (if not already authenticated) and accepts
the request of the application to get access as requested by the
specified scopes.
User gets redirected to the client application redirect URI, adding the following URL parameters to the provided redirect UI:
code=authorizationcode&
state=state-from-first-request
Write a HTML document to your HTTP listener, telling the user to close the browser and to return to your application. Close the localhost HTTP listener.
Make sure that the state URL parameter matches your state variable. If not, abort the authorization process.
Send the following request to the LOOK4 ID token endpoint to “trade in” the one-time authorization code (parameters sent forms-encoded as POST request):
POST /connect/token
client_id=client1&
grant_type=authorization_code&
code=authorization-code-received-via-redirect
code_verifier=code_verifier-variable
redirect_uri=http://127.0.0.1:xxxxx
In case of a valid request, the answer to this requests contains a small JSON document:
{
"access_token": "…",
"refresh_token": "…",
"token_type": "Bearer"
}
Persist the refresh token for later use in encrypted form. E.g. on Windows, use can use DPAPI (Data Protection API).
To get a new access token using the refresh token:
POST /connect/token
client_id=client1&
grant_type=refresh_token&
refresh_token=refresh-token
Server-Based Web Applications
Supported Flows
Please use the “Authorization code” flow (grant type authorization_code)
with a client secret. We will provide you with a secret upon registration.
Later on, you will be able to create and disable secrets
via a self-service client management portal in LOOK4 ID.
Registration
For registering this kind of client application, in addition to the
general information listed above (“Registering client applications”),
please include the following information in your request:
Offline access required? Whether long-lived refresh tokens will be provided or
not. Please note that all sign-ins requesting offline access need user consent.
At least one Redirect URI: The redirect URI used in your
authorization requests must match exactly one of these, including
protocol, port number etc. Please remember to include URIs also for
your development and testing environments if applicable.
Absolute Redirect URL Required
Please note that your application, for providing the correct redirect URL,
must be aware of its full external URL, including the correct protocol, domain name and port.
This is a common source of problems for web applications running behind a load balancer or proxy.
Low-level Client Implementation
Just in case that you really have to implement this without making use
of a OAuth library or framework, or you want to get an idea of what is going
on behind the scenes, these are the steps performed to obtain the token:
Do not forget URL encoding for all URL parameters!
On first use, the client redirects the user to the following LOOK4 ID URL:
GET /connect/authorize?
client_id=yourclientid&
scope=scope1 scope 2 scope3& //(see list of scopes below)
response_type=code&
redirect_uri=https://myapp/callback&
state=abc //(client-generated unique random value for CSRF protection)
User logs into LOOK4 ID (if not already authenticated) and accepts
the request of the application to get access as requested by the
specified scopes.
User gets redirected to the client application redirect URI, adding the following URL parameters to the provided redirect UI:
code=authorizationcode&
state=state-from-first-request //(clients should make sure that this matches the state in the original redirect to LOOK4 ID to protect their users from CSRF attacks)
The client application directly contacts the LOOK4 ID token endpoint to “trade in” the one-time authorization code (parameters sent forms-encoded as POST request):
POST /connect/token
client_id=client1&
client_secret=secret&
grant_type=authorization_code&
code=authorization-code-received-via-redirect
redirect_uri=https://myapp/callback
In case of a valid request, the answer to this requests contains a small JSON document:
{
"access_token": "…",
"refresh_token": "…",
"token_type": "Bearer"
}
To get a new access token using the refresh token:
POST /connect/token
client_id=client1&
client_secret=secret&
grant_type=refresh_token&
refresh_token=refresh-token
Example of a Low-Level Implementation in ASP.NET Core
This is for demonstration only. Normally, in ASP.NET Core you would want to use the built-in OAuth support.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace L4IDWebAppWithoutOAuthLib.Controllers
{
[Route("/")]
public class HomeController : Controller
{
private const string ClientId = "...";
private const string ClientSecret = "...";
private const string Scopes = "offline_access l4o.supplierreg.readwrite";
private static readonly Uri L4IDBaseUrl = new Uri("https://auth-test.look4optics.com");
private static readonly Uri L4OAPIBaseUrl = new Uri("https://api-test.look4optics.com");
private static readonly HttpClient L4IDClient = new HttpClient() { BaseAddress = L4IDBaseUrl };
private static readonly HttpClient L4OAPIClient = new HttpClient() { BaseAddress = L4OAPIBaseUrl };
// Redirect user to LOOK4 ID for sign-in
[HttpGet("login")]
public IActionResult Login()
{
// generate unique state to prevent e.g. CSRF
string state = null;
using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
{
byte[] randomBytes = new byte[16];
rng.GetBytes(randomBytes);
state = Convert.ToBase64String(randomBytes);
}
TempData["oauth-state"] = state; // could be persited in server-side session or cookie
StringBuilder urlParamsBuilder = new StringBuilder();
urlParamsBuilder.Append($"?client_id={Uri.EscapeDataString(ClientId)}");
urlParamsBuilder.Append($"&scope={Uri.EscapeDataString(Scopes)}");
urlParamsBuilder.Append($"&response_type=code");
urlParamsBuilder.Append($"&redirect_uri={Uri.EscapeDataString(getCallbackUrl())}");
urlParamsBuilder.Append($"&state={Uri.EscapeDataString(state)}");
// nonce only required for OpenID Connect
Uri authorizeUrl = new Uri(L4IDBaseUrl, $"/connect/authorize{urlParamsBuilder}");
return Redirect(authorizeUrl.ToString());
}
[HttpGet("callback")]
public async Task<IActionResult> Callback([FromQuery(Name = "code")] string code, [FromQuery(Name = "state")] string state)
{
// state validation
if (string.IsNullOrEmpty(state))
{
return BadRequest("no state returned");
}
string expectedState = TempData["oauth-state"]?.ToString();
if (string.IsNullOrEmpty(expectedState))
{
return BadRequest("no state available for this session");
}
if (state != expectedState)
{
return BadRequest("Invalid state");
}
if (string.IsNullOrEmpty(code))
{
return BadRequest("no code provided");
}
// use authorization code to get access and/or refresh token from L4ID token endpoint
var tokenRequest = new FormUrlEncodedContent(new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("client_id", ClientId),
new KeyValuePair<string, string>("client_secret", ClientSecret),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", getCallbackUrl())
});
var tokenResponse = await L4IDClient.PostAsync("/connect/token", tokenRequest);
var tokenResponseJsonDoc = await JsonDocument.ParseAsync(await tokenResponse.Content.ReadAsStreamAsync());
if (!tokenResponse.IsSuccessStatusCode)
{
return BadRequest(tokenResponseJsonDoc);
}
// TODO: if your requested and accepted scopes include "offline_access",
// you will get a refresh token that can be persisted for this user e.g. in a database
// and used to get a fresh access token any time later
var refreshToken = tokenResponseJsonDoc.RootElement.GetProperty("refresh_token").GetString();
// use can use this to access L4OAPI for up to 1h right away
var accessToken = tokenResponseJsonDoc.RootElement.GetProperty("access_token").GetString();
// example: get supplier registrations and return the raw JSON result
var supplierRegistrationRequest = new HttpRequestMessage(HttpMethod.Get, "/v2/supplierregistrations");
supplierRegistrationRequest.Headers.Add("Authorization", $"Bearer {accessToken}");
supplierRegistrationRequest.Headers.Add("Accept", MediaTypeNames.Application.Json);
var supplierRegistrationResponse = await L4OAPIClient.SendAsync(supplierRegistrationRequest);
return Content(
await supplierRegistrationResponse.Content.ReadAsStringAsync(),
MediaTypeNames.Application.Json);
}
private string getCallbackUrl()
{
// absolute URL; this must be defined via configuration if your application is running behind a load balancer or reverse proxy!
return Url.Action("callback", null, null, Request.Scheme);
}
}
}
Backend or Batch Applications
Applications that do not act on behalf of a LOOK4 ID user
and do not need or support interactive login.
A typical use case in context of the LOOK4 Optics API would be backend
or batch processes used by suppliers, or ECPs running
their own custom software processes.
Supported Flows
Please use the “Client credentials” flow (grant type client_credentials)
with a client secret. We will provide you with a secret upon registration.
Later on, you will be able to create and disable secrets
via a self-service client management portal in LOOK4 ID.
Limitations
All clients of this type must be confidential (i.e. server-based), and
permissions (claims) granted to the client will be managed by LOOK4
internally.
This type of client cannot (and doesn’t need to) be an “offline client”
and cannot use refresh tokens.
This type of client cannot use OIDC or any OpenID-related scopes (as
there is no user identity).
Registration
For registering this kind of client application, in addition to the
general information listed above (“Registering client applications”),
please include the following information in your request:
Example using Bash, cURL, jq
# configure environment
token_endpoint="https://auth-test.look4optics.com/connect/token"
api_base_url="https://api-test.look4optics.com"
# L4IDv2 client config
client_id="..."
client_secret="..."
scopes="l4o.catalogs.read l4o.supplierreg.read"
# get access token
access_token=$(curl --silent \
--data client_id=$client_id \
--data client_secret=$client_secret \
--data grant_type="client_credentials" \
--data scope="$scopes" \
-X POST $token_endpoint \
| jq -r '.access_token')
# use access token to get current supplier registrations
curl -i \
-H "Authorization: Bearer $access_token" \
"$api_base_url/v2/supplierregistrations"
Example using .NET
This example is using the
IdentityModel library.
Please refer to the documentation of your favorite authentication
library on how to obtain a token using other libraries.
using IdentityModel.Client;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace Client
{
public class Program
{
private static async Task Main()
{
using (var client = new HttpClient())
{
// discover endpoints from metadata
var disco = await
client.GetDiscoveryDocumentAsync("https://auth-test.look4optics.com").ConfigureAwait(false);
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new
ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "quickstart-console",
ClientSecret = "your-client-id",
Scope = "l4o.catalogs.read"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
// call api
client.SetBearerToken(tokenResponse.AccessToken);
var response = await
client.GetAsync("https://api-test.look4optics.com/v2/catalogs/");
Console.WriteLine(response.StatusCode);
}
}
}
}
OpenID Connect (OIDC) support
If an application does not only want to make API calls on behalf of the user,
but also needs to know the user’s identity, OIDC allows to authenticate the user.
This is only supported with the Autorization code and Implicit flows.
Please remember to include the OIDC-related scopes you might need
(at least openid, optionally profile, email), when registering your client
and when requesting the token.
Instead of requesting the id_token response type,
please use the access token to request profile data from the User Info Endpoint.
Token revocation
Revoking (short-lived) access tokens is not supported,
as LOOK4 ID is using JWT (i.e. signed, self-contained) tokens.
Revoking (long-lived) refresh tokens can be useful if you want to enforce a user to
reauthenticate with LOOK4 ID, or if a user is no longer using your or
our service.
Note: If you are aware of a security incident involving LOOK4 ID tokens, e.g.
tokens possible being exposed to unauthorized users or the public, in
addition to revoking long-lived tokens, please always inform our support
team so that we can search for abuse, analyze threats and take steps to
protect our users and partners.
Using OAuth (according to RFC 7009)
You can either use your OAuth client library to do so, or, if you like
to issue a raw HTTP call, use the following call to the revocation
endpoint:
For confidential clients (with a client secret):
POST /connect/revocation
Content-Type: application/x-www-form-urlencoded
Authorization: Basic client-id-and-secret
token=token-to-be-revoked&token_type_hint=refresh_token
For public clients (without a client secret):
POST /connect/revocation
Content-Type: application/x-www-form-urlencoded
token=token-to-be-revoked&token_type_hint=refresh_token&client_id=clientid
Using LOOK4 ID Portal
Users can manage their own consents and revoke access to applications.
When the user revokes access to an application, associated refresh tokens are revoked automatically.
Scopes
When requesting multiple scopes, separate scope names with spaces.
Special scopes (independent from the protected resource):
Please refer to the LOOK4 Optics API documentation for a complete list
of scopes supported.
Localization
The LOOK4 ID portal automatically decides the default language from the
browser’s Accept-Language header. If you want to ensure that the LOOK4
ID language is consistent with the language of your client application,
add the ui_locales request parameter to the query string when
redirecting users to LOOK4 ID for authentication. You can include
multiple values as a space-separated list, ordered by preference.
Example (without URL encoding):
Supported UI languages
Currently, the LOOK4 ID user interface is available in English language.
A German translation is in development. Further (european) languages
will be added according to the needs of our partners.
Currently supported values for ui_locales: