Classes
The following classes are available globally.
-
To test AuthWebViewController without any actual provider.
- It spends some time in the “preparing” state sometimes skipping it.
- Then pretends that it got an endpoint (an inline page) with a link to a redirect URL.
Tapping the link should cause the web view to intercept it and notify the view model via
handleRedirect()
. - The latter is going to jump to a “finalizing” state spending some time there (possibly skipping it) and then jumping to “completed”. And of course it can randomly fail at any step.
-
A basic in-app browser to use with OAuth/OpenID flows. Nothing special here, you can make your own or direct the user to an external browser, for example.
See more -
Basic OAuth 2.0 client supporting OpenID as well.
OAuth Refresher
The app (“client”) wants to get access to protected resources of the end-user and thus needs a permission from them in the form of an access token, a string that the app is going to show to the server storing those resources.
In order to obtain the access token the app opens a web browser (in-app or external) and navigates it to the corresponding “authorization server”. It tells the server what kind of app it is (“client identifier”), what resources the talk is about (“scope”), and how the credentials should be returned back (“response type”).
The end-user authenticates themselves on this page and confirms that they are OK with the app accessing the resources in question.
The server responds back to the app by directing the browser to a “redirect URL” mentioned by the app earlier adding its response in “query” or “fragment” parts of this URL.
The app then either directly finds the access token in this response URL (“implicit flow”) or uses a short-lived code from there to get the access token via a separate network call to the “token endpoint” (“authorization code flow”).
Note that the extra step about code seems like having no sense for a native app. This is because it was designed for web servers and similar clients allowing them to obtain access tokens without the end-user (and thus any malicious code running on their device) seeing them. Note also that the access tokens usually expire but might be refreshed using a “refresh token”. A quirk of the protocol is that this token, if available, can be obtained only via “authorization code flow”.
OpenID Refresher
OpenID is essentially an OAuth flow where the resource being accessed is some basic information about the user. Implementation-wise the “scope” of such a flow includes “openid” and a more fancy JWT ID Token is returned in addition to (or instead of) the usual access token. It’s an official replacement for ad-hoc OAuth flows used earlier to authenticate users of one service by asking them via OAuth for access to their email address or basic details let say on another service.
Usage
TLDR: call
start*()
and watch thestate
to become.authorized
helping with authorization UI when needed.Nothing happens when the client is initialized, i.e. it stays at
.idle
.Once
start()
and friends are called the client checks the storage for the existing credentials corresponding to the passed scope and response types:If credentials were found and are not expired or can be refreshed, then it directly switches to
.authorized
state. The user code now has access to the token(s).If no good credentials were found and a non-interactive (“silent”) mode was specified, then the client cancels the flow. The user code can treat this state as “not logged in” / “have no access”.
If no good credentials were found and an interactive mode was specified, then the client enters
.authorizing
state and expects the user code to help with authorization UI by presenting a browser (in-app or external).
(See
AuthWebViewController
in “MMMoth/UI” for a basic implementation.)The browser should navigate to the endpoint associated with
.authorizing
state. The user code should be able to intercept all the requests to the associated redirect URL and feed them back to the client viahandleAuthorizationRedirect()
; it should also report any errors opening the endpoint viahandleAuthorizationFailure()
and can cancel the flow viacancel()
.When the client gets information from authorization server via
handleAuthorizationRedirect()
, then it either:immediately fails the flow (in case the server returned an explicit error or provided an invalid response);
or directly enters
.authorized
state (in case of an “implicit” flow, that is when responseType does NOT include.code
);or begins exchanging the authorization code to token(s) (in case of an “authorization code” flow, that is when responseType does include
.code
).
A picture for the above:
┌─────────────────┐ │ idle │ └─────────────────┘ │ ▼ ┏━━━━━━━━━━━━━━━━━┓ start() ─────────────────▶┃ authorized ┃ Have credentials ┗━━━━━━━━━━━━━━━━━┛ │ in the storage ▼ No good credentials ┌─────────────────┐ in the storage ────────────▶│ cancelled │ │ Silent mode └─────────────────┘ │ Interactive mode │ ▼ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ The user code opens a browser authorizing and directs it to the └ ─ ─ ─ ─ ─ ─ ─ ─ ┘ specified URL. The user code managing the browser calls either of these: ┌─────────────────┐ cancel()─▶│ cancelled │ └─────────────────┘ ┌─────────────────┐ handleAuthorizationFailure()─▶│ failed │ └─────────────────┘ ▲ handleAuthorizationRedirect() │ Implicit │ Auth Code │ │ │ flow │ or Hybrid │ │ flow ▼ └ ─ ─ ─ ─ ─ ┤ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ fetchingToken ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ │ ▼ │ ┏━━━━━━━━━━━━━━━━━┓ └───▶┃ authorized ┃ ┗━━━━━━━━━━━━━━━━━┛
Notes
Initially I wanted to have an OpenID client that would be using an OAuth client under the hood, but this would be more complicated without the OAuth client knowing of OpenID-specific parameters.
See more -
OpenID config that’s immediately available.
See more -
OpenID config that can be fetched from a provider according to OpenID Connect Discovery conventions. See [https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig].
See more -
Simple parser for ID Tokens, which are non-encrypted JSON Web Tokens.
In the context of MMMoth library we are only interested in expiration time field, just to know when to refresh the token. We are not concerned with verification, it’s something for the backend accepting the tokens. We don’t want support for generic JWTs either and thus can require some of the fields avoiding optionals.
See more